From a57bbfbc3bd3625e220b02a9978020a358cf4644 Mon Sep 17 00:00:00 2001 From: Zita Szupera Date: Mon, 10 Mar 2025 16:48:44 +0100 Subject: [PATCH 1/2] feat: all components use on push change detection --- .eslintrc.json | 2 +- .../customizations-example/.eslintrc.json | 3 +- .../src/app/icon/icon.component.spec.ts | 7 +- .../message-action.component.spec.ts | 7 +- .../thread-header.component.spec.ts | 7 +- projects/sample-app/.eslintrc.json | 3 +- projects/sample-app/src/styles.scss | 1 + .../attachment-list.component.spec.ts | 7 +- .../attachment-list.component.ts | 9 +- .../attachment-preview-list.component.html | 1 + .../attachment-preview-list.component.spec.ts | 7 +- .../attachment-preview-list.component.ts | 20 +- .../avatar-placeholder.component.spec.ts | 7 +- .../avatar-placeholder.component.ts | 8 +- .../src/lib/avatar/avatar.component.spec.ts | 9 +- .../src/lib/avatar/avatar.component.ts | 19 +- .../channel-header.component.spec.ts | 3 + .../channel-header.component.ts | 49 +++-- .../channel-list/channel-list.component.html | 3 +- .../channel-list.component.spec.ts | 3 + .../channel-list/channel-list.component.ts | 19 +- .../channel-preview.component.spec.ts | 46 ++++ .../channel-preview.component.ts | 207 ++++++++++++++---- .../src/lib/channel/channel.component.ts | 3 +- .../icon-placeholder.component.spec.ts | 7 +- .../icon-placeholder.component.ts | 8 +- .../src/lib/icon/icon.component.ts | 3 +- ...loading-indicator-placeholder.component.ts | 3 +- .../loading-indicator.component.ts | 3 +- .../message-actions-box.component.spec.ts | 8 +- .../message-actions-box.component.ts | 6 +- .../message-bounce-prompt.component.spec.ts | 7 +- .../message-bounce-prompt.component.ts | 28 ++- .../autocomplete-textarea.component.spec.ts | 8 +- .../autocomplete-textarea.component.ts | 162 ++++++++------ .../message-input.component.spec.ts | 9 +- .../message-input/message-input.component.ts | 40 +++- .../textarea/textarea.component.spec.ts | 8 +- .../textarea/textarea.component.ts | 43 ++-- .../message-list.component.spec.ts | 9 +- .../message-list/message-list.component.ts | 32 +-- ...ssage-reactions-selector.component.spec.ts | 7 +- .../message-reactions-selector.component.ts | 4 +- .../message-reactions.component.spec.ts | 8 +- .../message-reactions.component.ts | 11 +- .../message-text.component.spec.ts | 8 +- .../message-text/message-text.component.ts | 9 +- .../src/lib/message/message.component.ts | 6 +- .../src/lib/mocks/index.ts | 6 +- .../src/lib/modal/modal.component.spec.ts | 8 +- .../src/lib/modal/modal.component.ts | 2 + .../notification-list.component.spec.ts | 7 +- .../notification-list.component.ts | 3 +- .../notification.component.spec.ts | 7 +- .../notification/notification.component.ts | 8 +- .../paginated-list.component.spec.ts | 8 +- .../paginated-list.component.ts | 2 + .../src/lib/thread/thread.component.spec.ts | 7 +- .../src/lib/thread/thread.component.ts | 57 +++-- .../lib/user-list/user-list.component.spec.ts | 7 +- .../src/lib/user-list/user-list.component.ts | 9 +- .../voice-recorder-wavebar.component.spec.ts | 7 +- .../voice-recorder-wavebar.component.ts | 3 +- .../voice-recorder.component.spec.ts | 7 +- .../voice-recorder.component.ts | 24 +- .../voice-recording-wavebar.component.spec.ts | 7 +- .../voice-recording-wavebar.component.ts | 2 + .../voice-recording.component.spec.ts | 8 +- .../voice-recording.component.ts | 2 + 69 files changed, 810 insertions(+), 268 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 635b91e1..951a22a1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -17,7 +17,7 @@ ], "rules": { "@angular-eslint/sort-ngmodule-metadata-arrays": "off", - "@angular-eslint/prefer-on-push-component-change-detection": "off", + "@angular-eslint/prefer-on-push-component-change-detection": 2, "unused-imports/no-unused-imports": "error", "no-console": [ "error", diff --git a/projects/customizations-example/.eslintrc.json b/projects/customizations-example/.eslintrc.json index d6408983..9fed81f6 100644 --- a/projects/customizations-example/.eslintrc.json +++ b/projects/customizations-example/.eslintrc.json @@ -27,7 +27,8 @@ "prefix": "app", "style": "kebab-case" } - ] + ], + "@angular-eslint/prefer-on-push-component-change-detection": "off" } }, { diff --git a/projects/customizations-example/src/app/icon/icon.component.spec.ts b/projects/customizations-example/src/app/icon/icon.component.spec.ts index a833e3a5..65f17ed3 100644 --- a/projects/customizations-example/src/app/icon/icon.component.spec.ts +++ b/projects/customizations-example/src/app/icon/icon.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ChangeDetectionStrategy } from '@angular/core'; import { IconComponent } from './icon.component'; @@ -9,7 +10,11 @@ describe('IconComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [IconComponent], - }).compileComponents(); + }) + .overrideComponent(IconComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/projects/customizations-example/src/app/message-action/message-action.component.spec.ts b/projects/customizations-example/src/app/message-action/message-action.component.spec.ts index d336c6cf..5db958d3 100644 --- a/projects/customizations-example/src/app/message-action/message-action.component.spec.ts +++ b/projects/customizations-example/src/app/message-action/message-action.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ChangeDetectionStrategy } from '@angular/core'; import { MessageActionComponent } from './message-action.component'; @@ -9,7 +10,11 @@ describe('MessageActionComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [MessageActionComponent], - }).compileComponents(); + }) + .overrideComponent(MessageActionComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/projects/customizations-example/src/app/thread-header/thread-header.component.spec.ts b/projects/customizations-example/src/app/thread-header/thread-header.component.spec.ts index b40aa7b7..02bbd393 100644 --- a/projects/customizations-example/src/app/thread-header/thread-header.component.spec.ts +++ b/projects/customizations-example/src/app/thread-header/thread-header.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ChangeDetectionStrategy } from '@angular/core'; import { ThreadHeaderComponent } from './thread-header.component'; @@ -9,7 +10,11 @@ describe('ThreadHeaderComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ThreadHeaderComponent], - }).compileComponents(); + }) + .overrideComponent(ThreadHeaderComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/projects/sample-app/.eslintrc.json b/projects/sample-app/.eslintrc.json index 548d7760..cfef68cf 100644 --- a/projects/sample-app/.eslintrc.json +++ b/projects/sample-app/.eslintrc.json @@ -33,7 +33,8 @@ { "patterns": ["dist/*", "public-api"] } - ] + ], + "@angular-eslint/prefer-on-push-component-change-detection": "off" } }, { diff --git a/projects/sample-app/src/styles.scss b/projects/sample-app/src/styles.scss index 47c295cc..ef62810c 100644 --- a/projects/sample-app/src/styles.scss +++ b/projects/sample-app/src/styles.scss @@ -61,6 +61,7 @@ body { .channel-list.thread-close { width: 30%; + min-width: 30%; max-width: 420px; position: initial; z-index: auto; diff --git a/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.spec.ts b/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.spec.ts index 2f3238e3..7d3fad3b 100644 --- a/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.spec.ts @@ -15,6 +15,7 @@ import { CustomAttachmentListContext, GalleryAttachment } from '../types'; import { AttachmentConfigurationService } from '../attachment-configuration.service'; import { AfterViewInit, + ChangeDetectionStrategy, Component, SimpleChange, TemplateRef, @@ -63,7 +64,11 @@ describe('AttachmentListComponent', () => { MessageService, ], imports: [TranslateModule.forRoot()], - }).compileComponents(); + }) + .overrideComponent(AttachmentListComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); TestBed.inject(StreamI18nService).setTranslation(); }); diff --git a/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts b/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts index 050089fd..00e1f141 100644 --- a/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts +++ b/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts @@ -1,4 +1,6 @@ import { + ChangeDetectionStrategy, + ChangeDetectorRef, Component, EventEmitter, HostBinding, @@ -36,6 +38,7 @@ import { MessageService } from '../message.service'; selector: 'stream-attachment-list', templateUrl: './attachment-list.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AttachmentListComponent implements OnChanges, OnInit, OnDestroy { /** @@ -77,6 +80,7 @@ export class AttachmentListComponent implements OnChanges, OnInit, OnDestroy { private channelService: ChannelService, private attachmentConfigurationService: AttachmentConfigurationService, private messageService: MessageService, + private cdRef: ChangeDetectorRef, ) {} trackByUrl = (attachment: Attachment | GalleryAttachment) => { @@ -119,7 +123,10 @@ export class AttachmentListComponent implements OnChanges, OnInit, OnDestroy { ngOnInit(): void { this.subscriptions.push( this.customTemplatesService.customAttachmentListTemplate$.subscribe( - (t) => (this.customAttachmentsTemplate = t), + (t) => { + this.customAttachmentsTemplate = t; + this.cdRef.markForCheck(); + }, ), ); } diff --git a/projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.html b/projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.html index 1d8cb93d..313f2892 100644 --- a/projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.html +++ b/projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.html @@ -72,6 +72,7 @@ diff --git a/projects/stream-chat-angular/src/lib/channel-list/channel-list.component.spec.ts b/projects/stream-chat-angular/src/lib/channel-list/channel-list.component.spec.ts index 1a869412..9449e3a8 100644 --- a/projects/stream-chat-angular/src/lib/channel-list/channel-list.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/channel-list/channel-list.component.spec.ts @@ -14,6 +14,7 @@ import { ChannelListComponent } from './channel-list.component'; import { Subject, of } from 'rxjs'; import { PaginatedListComponent } from '../paginated-list/paginated-list.component'; import { Channel } from 'stream-chat'; +import { ChangeDetectionStrategy } from '@angular/core'; describe('ChannelListComponent', () => { let channelServiceMock: MockChannelService; @@ -46,6 +47,8 @@ describe('ChannelListComponent', () => { }, ThemeService, ], + }).overrideComponent(ChannelListComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, }); fixture = TestBed.createComponent(ChannelListComponent); nativeElement = fixture.nativeElement as HTMLElement; diff --git a/projects/stream-chat-angular/src/lib/channel-list/channel-list.component.ts b/projects/stream-chat-angular/src/lib/channel-list/channel-list.component.ts index f91b39df..f84752cd 100644 --- a/projects/stream-chat-angular/src/lib/channel-list/channel-list.component.ts +++ b/projects/stream-chat-angular/src/lib/channel-list/channel-list.component.ts @@ -1,5 +1,5 @@ -import { Component, OnDestroy, TemplateRef } from '@angular/core'; -import { Observable, Subscription } from 'rxjs'; +import { ChangeDetectionStrategy, Component, TemplateRef } from '@angular/core'; +import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { Channel } from 'stream-chat'; import { ChannelService } from '../channel.service'; @@ -14,8 +14,9 @@ import { ChannelPreviewContext } from '../types'; selector: 'stream-channel-list', templateUrl: './channel-list.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ChannelListComponent implements OnDestroy { +export class ChannelListComponent { channels$: Observable; isError$: Observable; isInitializing$: Observable; @@ -23,11 +24,10 @@ export class ChannelListComponent implements OnDestroy { hasMoreChannels$: Observable; customChannelPreviewTemplate: TemplateRef | undefined; theme$: Observable; - subscriptions: Subscription[] = []; constructor( private channelService: ChannelService, - private customTemplatesService: CustomTemplatesService, + public readonly customTemplatesService: CustomTemplatesService, private themeService: ThemeService, ) { this.theme$ = this.themeService.theme$; @@ -37,15 +37,6 @@ export class ChannelListComponent implements OnDestroy { this.isInitializing$ = this.channelService.channelQueryState$.pipe( map((s) => !this.isLoadingMoreChannels && s?.state === 'in-progress'), ); - this.subscriptions.push( - this.customTemplatesService.channelPreviewTemplate$.subscribe( - (template) => (this.customChannelPreviewTemplate = template), - ), - ); - } - - ngOnDestroy(): void { - this.subscriptions.forEach((s) => s.unsubscribe()); } async loadMoreChannels() { diff --git a/projects/stream-chat-angular/src/lib/channel-preview/channel-preview.component.spec.ts b/projects/stream-chat-angular/src/lib/channel-preview/channel-preview.component.spec.ts index 99a22cdd..ce32f4dc 100644 --- a/projects/stream-chat-angular/src/lib/channel-preview/channel-preview.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/channel-preview/channel-preview.component.spec.ts @@ -17,6 +17,7 @@ import { Observable, Subject, of } from 'rxjs'; import { DateParserService } from '../date-parser.service'; import { IconModule } from '../icon/icon.module'; import { IconPlaceholderComponent } from '../icon/icon-placeholder/icon-placeholder.component'; +import { ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; describe('ChannelPreviewComponent', () => { let fixture: ComponentFixture; @@ -52,6 +53,10 @@ describe('ChannelPreviewComponent', () => { { provide: ChannelService, useValue: channelServiceMock }, { provide: ChatClientService, useValue: chatClientServiceMock }, ], + }).overrideComponent(ChannelPreviewComponent, { + set: { + changeDetection: ChangeDetectionStrategy.OnPush, + }, }); fixture = TestBed.createComponent(ChannelPreviewComponent); component = fixture.componentInstance; @@ -218,6 +223,46 @@ describe('ChannelPreviewComponent', () => { expect(avatar.location).toBe('channel-preview'); }); + it('should update title, avatarImage, and avatarName when channel is updated', () => { + // Setup initial channel + const channel = generateMockChannels()[0]; + component.channel = channel; + fixture.detectChanges(); + + // Verify initial values + expect(component.title).toBe(channel.data?.name); + expect(component.avatarImage).toBe(channel.data?.image); + expect(component.avatarName).toBe(channel.data?.name); + + // Update channel data + const updatedName = 'Updated Channel Name'; + const updatedImage = 'https://example.com/updated-image.jpg'; + + // Simulate channel update event + channel.data = { + ...channel.data, + name: updatedName, + image: updatedImage, + }; + + channel.handleEvent('channel.updated', { + channel: channel, + }); + + fixture.detectChanges(); + + // Verify updated values + expect(component.title).toBe(updatedName); + expect(component.avatarImage).toBe(updatedImage); + expect(component.avatarName).toBe(updatedName); + + // Verify UI is updated + expect(queryTitle()?.textContent).toContain(updatedName); + const avatar = queryAvatar(); + expect(avatar.imageUrl).toBe(updatedImage); + expect(avatar.name).toBe(updatedName); + }); + it('should display channel display text', () => { const channel = generateMockChannels()[0]; channel.data!.name = undefined; @@ -371,6 +416,7 @@ describe('ChannelPreviewComponent', () => { ); component.displayAs = 'html'; + fixture.debugElement.injector.get(ChangeDetectorRef).detectChanges(); fixture.detectChanges(); expect( diff --git a/projects/stream-chat-angular/src/lib/channel-preview/channel-preview.component.ts b/projects/stream-chat-angular/src/lib/channel-preview/channel-preview.component.ts index b034f6a5..dd06fd0b 100644 --- a/projects/stream-chat-angular/src/lib/channel-preview/channel-preview.component.ts +++ b/projects/stream-chat-angular/src/lib/channel-preview/channel-preview.component.ts @@ -1,4 +1,12 @@ -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; import { Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; import { Channel, Event, FormatMessageResponse } from 'stream-chat'; @@ -19,8 +27,11 @@ import { DateParserService } from '../date-parser.service'; selector: 'stream-channel-preview', templateUrl: './channel-preview.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ChannelPreviewComponent implements OnInit, OnDestroy { +export class ChannelPreviewComponent + implements OnInit, AfterViewInit, OnDestroy +{ /** * The channel to be displayed */ @@ -35,8 +46,13 @@ export class ChannelPreviewComponent implements OnInit, OnDestroy { latestMessage?: FormatMessageResponse; displayAs: 'text' | 'html'; userId?: string; + avatarImage: string | undefined; + avatarName: string | undefined; + title: string | undefined = ''; + private subscriptions: (Subscription | { unsubscribe: () => void })[] = []; private canSendReadEvents = true; + private isViewInitialized = false; constructor( private channelService: ChannelService, @@ -44,6 +60,7 @@ export class ChannelPreviewComponent implements OnInit, OnDestroy { messageService: MessageService, public customTemplatesService: CustomTemplatesService, private dateParser: DateParserService, + private cdRef: ChangeDetectorRef, ) { this.displayAs = messageService.displayAs; } @@ -53,14 +70,22 @@ export class ChannelPreviewComponent implements OnInit, OnDestroy { this.chatClientService.user$.subscribe((user) => { if (user?.id !== this.userId) { this.userId = user?.id; + if (this.isViewInitialized) { + this.cdRef.markForCheck(); + } } }), ); this.subscriptions.push( - this.channelService.activeChannel$.subscribe( - (activeChannel) => - (this.isActive = activeChannel?.id === this.channel?.id), - ), + this.channelService.activeChannel$.subscribe((activeChannel) => { + const isActive = activeChannel?.id === this.channel?.id; + if (isActive !== this.isActive) { + this.isActive = isActive; + if (this.isViewInitialized) { + this.cdRef.markForCheck(); + } + } + }), ); const messages = this.channel?.state?.latestMessages; if (messages && messages.length > 0) { @@ -70,21 +95,42 @@ export class ChannelPreviewComponent implements OnInit, OnDestroy { const capabilities = (this.channel?.data?.own_capabilities as string[]) || []; this.canSendReadEvents = capabilities.indexOf('read-events') !== -1; + + this.updateChannelProperties(); + this.subscriptions.push( - this.channel!.on('message.new', this.handleMessageEvent.bind(this)), + this.channel!.on('message.new', (event) => { + this.handleMessageEvent(event); + }), ); this.subscriptions.push( - this.channel!.on('message.updated', this.handleMessageEvent.bind(this)), + this.channel!.on('message.updated', (event) => { + this.handleMessageEvent(event); + }), ); this.subscriptions.push( - this.channel!.on('message.deleted', this.handleMessageEvent.bind(this)), + this.channel!.on('message.deleted', (event) => { + this.handleMessageEvent(event); + }), + ); + this.subscriptions.push( + this.channel!.on('channel.truncated', (event) => { + this.handleMessageEvent(event); + }), ); this.subscriptions.push( - this.channel!.on('channel.truncated', this.handleMessageEvent.bind(this)), + this.channel!.on('channel.updated', (event) => { + this.handleChannelUpdatedEvent(event); + }), ); this.subscriptions.push( this.channel!.on('message.read', () => { - this.isUnreadMessageWasCalled = false; + if (this.isUnreadMessageWasCalled) { + this.isUnreadMessageWasCalled = false; + if (this.isViewInitialized) { + this.cdRef.markForCheck(); + } + } this.updateUnreadState(); }), ); @@ -98,32 +144,23 @@ export class ChannelPreviewComponent implements OnInit, OnDestroy { ), ) .subscribe(() => { - this.isUnreadMessageWasCalled = true; + if (!this.isUnreadMessageWasCalled) { + this.isUnreadMessageWasCalled = true; + if (this.isViewInitialized) { + this.cdRef.markForCheck(); + } + } this.updateUnreadState(); }), ); } - ngOnDestroy(): void { - this.subscriptions.forEach((s) => s.unsubscribe()); - } - - get avatarImage() { - return this.channel?.data?.image; + ngAfterViewInit(): void { + this.isViewInitialized = true; } - get avatarName() { - return this.channel?.data?.name; - } - - get title() { - if (!this.channel) { - return ''; - } - return getChannelDisplayText( - this.channel, - this.chatClientService.chatClient.user!, - ); + ngOnDestroy(): void { + this.subscriptions.forEach((s) => s.unsubscribe()); } setAsActiveChannel(): void { @@ -136,6 +173,9 @@ export class ChannelPreviewComponent implements OnInit, OnDestroy { this.latestMessageStatus = undefined; this.latestMessageText = 'streamChat.Nothing yet...'; this.latestMessageTime = undefined; + if (this.isViewInitialized) { + this.cdRef.markForCheck(); + } return; } const latestMessage = @@ -150,32 +190,53 @@ export class ChannelPreviewComponent implements OnInit, OnDestroy { } private setLatestMessage(message?: FormatMessageResponse) { - this.latestMessage = message; + let shouldUpdate = false; + if (this.latestMessage?.id !== message?.id) { + this.latestMessage = message; + shouldUpdate = true; + } + let latestMessageText = this.latestMessageText; if (message?.deleted_at) { - this.latestMessageText = 'streamChat.Message deleted'; + latestMessageText = 'streamChat.Message deleted'; } else if (message?.text) { - this.latestMessageText = + latestMessageText = getMessageTranslation( message, this.channel, this.chatClientService.chatClient.user, ) || message.text; } else if (message?.attachments && message.attachments.length) { - this.latestMessageText = 'streamChat.🏙 Attachment...'; + latestMessageText = 'streamChat.🏙 Attachment...'; + } + if (latestMessageText !== this.latestMessageText) { + this.latestMessageText = latestMessageText; + shouldUpdate = true; } + let latestMessageTime = this.latestMessageTime; if (this.latestMessage && this.latestMessage.type === 'regular') { - this.latestMessageTime = isOnSeparateDate( + latestMessageTime = isOnSeparateDate( new Date(), this.latestMessage.created_at, ) ? this.dateParser.parseDate(this.latestMessage.created_at) : this.dateParser.parseTime(this.latestMessage.created_at); } else { - this.latestMessageTime = undefined; + latestMessageTime = undefined; + } + if (latestMessageTime !== this.latestMessageTime) { + this.latestMessageTime = latestMessageTime; + shouldUpdate = true; + } + if (shouldUpdate) { + if (this.isViewInitialized) { + this.cdRef.markForCheck(); + } } } private updateUnreadState() { + let shouldUpdate = false; + let latestMessageStatus = this.latestMessageStatus; if ( this.channel && this.latestMessage && @@ -183,22 +244,84 @@ export class ChannelPreviewComponent implements OnInit, OnDestroy { this.latestMessage.status === 'received' && this.latestMessage.type === 'regular' ) { - this.latestMessageStatus = + latestMessageStatus = getReadBy(this.latestMessage, this.channel).length > 0 ? 'read' : 'delivered'; } else { - this.latestMessageStatus = undefined; + latestMessageStatus = undefined; } + if (latestMessageStatus !== this.latestMessageStatus) { + this.latestMessageStatus = latestMessageStatus; + shouldUpdate = true; + } + let unreadCount = this.unreadCount; + let isUnread = this.isUnread; if ( (this.isActive && !this.isUnreadMessageWasCalled) || !this.canSendReadEvents ) { - this.unreadCount = 0; - this.isUnread = false; + unreadCount = 0; + isUnread = false; + } else { + unreadCount = this.channel!.countUnread(); + isUnread = !!unreadCount; + } + + if (unreadCount !== this.unreadCount) { + this.unreadCount = unreadCount; + shouldUpdate = true; + } + if (isUnread !== this.isUnread) { + this.isUnread = isUnread; + shouldUpdate = true; + } + if (shouldUpdate) { + if (this.isViewInitialized) { + this.cdRef.markForCheck(); + } + } + } + + private handleChannelUpdatedEvent(event: Event) { + if (!this.channel || !event.channel) { return; } - this.unreadCount = this.channel!.countUnread(); - this.isUnread = !!this.unreadCount; + + this.updateChannelProperties(); + } + + private updateChannelProperties() { + let shouldUpdate = false; + const avatarImage = this.channel?.data?.image; + const avatarName = this.channel?.data?.name; + + if (avatarImage !== this.avatarImage) { + this.avatarImage = avatarImage; + shouldUpdate = true; + } + if (avatarName !== this.avatarName) { + this.avatarName = avatarName; + shouldUpdate = true; + } + + let title = this.title; + if (!this.channel) { + title = ''; + } else { + title = getChannelDisplayText( + this.channel, + this.chatClientService.chatClient.user!, + ); + } + if (title !== this.title) { + this.title = title; + shouldUpdate = true; + } + if (shouldUpdate) { + if (this.isViewInitialized) { + this.cdRef.markForCheck(); + } + } } } diff --git a/projects/stream-chat-angular/src/lib/channel/channel.component.ts b/projects/stream-chat-angular/src/lib/channel/channel.component.ts index 06c891c0..86f15539 100644 --- a/projects/stream-chat-angular/src/lib/channel/channel.component.ts +++ b/projects/stream-chat-angular/src/lib/channel/channel.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { combineLatest, Observable, Subscription } from 'rxjs'; import { map } from 'rxjs/operators'; import { ChannelService } from '../channel.service'; @@ -12,6 +12,7 @@ import { CustomTemplatesService } from '../custom-templates.service'; selector: 'stream-channel', templateUrl: './channel.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ChannelComponent { isError$: Observable; diff --git a/projects/stream-chat-angular/src/lib/icon/icon-placeholder/icon-placeholder.component.spec.ts b/projects/stream-chat-angular/src/lib/icon/icon-placeholder/icon-placeholder.component.spec.ts index 393ab8a6..b7c0e38e 100644 --- a/projects/stream-chat-angular/src/lib/icon/icon-placeholder/icon-placeholder.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/icon/icon-placeholder/icon-placeholder.component.spec.ts @@ -1,5 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; +import { ChangeDetectionStrategy } from '@angular/core'; import { IconPlaceholderComponent } from './icon-placeholder.component'; import { IconComponent } from '../icon.component'; @@ -11,7 +12,11 @@ describe('IconPlaceholderComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [IconPlaceholderComponent, IconComponent], - }).compileComponents(); + }) + .overrideComponent(IconPlaceholderComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/projects/stream-chat-angular/src/lib/icon/icon-placeholder/icon-placeholder.component.ts b/projects/stream-chat-angular/src/lib/icon/icon-placeholder/icon-placeholder.component.ts index 43f0b508..96b1189e 100644 --- a/projects/stream-chat-angular/src/lib/icon/icon-placeholder/icon-placeholder.component.ts +++ b/projects/stream-chat-angular/src/lib/icon/icon-placeholder/icon-placeholder.component.ts @@ -1,4 +1,9 @@ -import { Component, Input, OnChanges } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + Input, + OnChanges, +} from '@angular/core'; import { Icon } from '../icon.component'; import { IconContext } from '../../types'; import { CustomTemplatesService } from '../../custom-templates.service'; @@ -10,6 +15,7 @@ import { CustomTemplatesService } from '../../custom-templates.service'; selector: 'stream-icon-placeholder', templateUrl: './icon-placeholder.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class IconPlaceholderComponent implements OnChanges { /** diff --git a/projects/stream-chat-angular/src/lib/icon/icon.component.ts b/projects/stream-chat-angular/src/lib/icon/icon.component.ts index 6a788b83..7b75555b 100644 --- a/projects/stream-chat-angular/src/lib/icon/icon.component.ts +++ b/projects/stream-chat-angular/src/lib/icon/icon.component.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; export type Icon = | 'action' @@ -31,6 +31,7 @@ export type Icon = selector: 'stream-icon', templateUrl: './icon.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class IconComponent { /** diff --git a/projects/stream-chat-angular/src/lib/icon/loading-indicator-placeholder/loading-indicator-placeholder.component.ts b/projects/stream-chat-angular/src/lib/icon/loading-indicator-placeholder/loading-indicator-placeholder.component.ts index 7a1501dc..d0112bc5 100644 --- a/projects/stream-chat-angular/src/lib/icon/loading-indicator-placeholder/loading-indicator-placeholder.component.ts +++ b/projects/stream-chat-angular/src/lib/icon/loading-indicator-placeholder/loading-indicator-placeholder.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { CustomTemplatesService } from '../../custom-templates.service'; /** @@ -8,6 +8,7 @@ import { CustomTemplatesService } from '../../custom-templates.service'; selector: 'stream-loading-indicator-placeholder', templateUrl: './loading-indicator-placeholder.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class LoadingIndicatorPlaceholderComponent { constructor(public customTemplatesService: CustomTemplatesService) {} diff --git a/projects/stream-chat-angular/src/lib/icon/loading-indicator/loading-indicator.component.ts b/projects/stream-chat-angular/src/lib/icon/loading-indicator/loading-indicator.component.ts index 30787866..ed63f1eb 100644 --- a/projects/stream-chat-angular/src/lib/icon/loading-indicator/loading-indicator.component.ts +++ b/projects/stream-chat-angular/src/lib/icon/loading-indicator/loading-indicator.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; /** * The `LoadingIndicator` component displays a spinner to indicate that an action is in progress. @@ -7,6 +7,7 @@ import { Component } from '@angular/core'; selector: 'stream-loading-indicator', templateUrl: './loading-indicator.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class LoadingIndicatorComponent { constructor() {} diff --git a/projects/stream-chat-angular/src/lib/message-actions-box/message-actions-box.component.spec.ts b/projects/stream-chat-angular/src/lib/message-actions-box/message-actions-box.component.spec.ts index 085fa054..caae695b 100644 --- a/projects/stream-chat-angular/src/lib/message-actions-box/message-actions-box.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/message-actions-box/message-actions-box.component.spec.ts @@ -1,4 +1,4 @@ -import { SimpleChange } from '@angular/core'; +import { SimpleChange, ChangeDetectionStrategy } from '@angular/core'; import { ComponentFixture, fakeAsync, @@ -82,7 +82,11 @@ describe('MessageActionsBoxComponent', () => { useValue: TextareaComponent, }, ], - }).compileComponents(); + }) + .overrideComponent(MessageActionsBoxComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/projects/stream-chat-angular/src/lib/message-actions-box/message-actions-box.component.ts b/projects/stream-chat-angular/src/lib/message-actions-box/message-actions-box.component.ts index 71a6c0a6..06d5ec43 100644 --- a/projects/stream-chat-angular/src/lib/message-actions-box/message-actions-box.component.ts +++ b/projects/stream-chat-angular/src/lib/message-actions-box/message-actions-box.component.ts @@ -1,5 +1,6 @@ import { AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, @@ -27,6 +28,7 @@ import { MessageActionsService } from '../message-actions.service'; selector: 'stream-message-actions-box', templateUrl: './message-actions-box.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MessageActionsBoxComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit @@ -83,7 +85,7 @@ export class MessageActionsBoxComponent this.customActions = actions; this.setVisibleActions(); if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } }), ); @@ -96,7 +98,7 @@ export class MessageActionsBoxComponent if (isEditModalOpen !== this.isEditModalOpen) { this.isEditModalOpen = isEditModalOpen; if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } } }), diff --git a/projects/stream-chat-angular/src/lib/message-bounce-prompt/message-bounce-prompt.component.spec.ts b/projects/stream-chat-angular/src/lib/message-bounce-prompt/message-bounce-prompt.component.spec.ts index a18935c9..0d087087 100644 --- a/projects/stream-chat-angular/src/lib/message-bounce-prompt/message-bounce-prompt.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/message-bounce-prompt/message-bounce-prompt.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ChangeDetectionStrategy } from '@angular/core'; import { MessageBouncePromptComponent } from './message-bounce-prompt.component'; import { generateMockMessages } from '../mocks'; @@ -20,7 +21,11 @@ describe('MessageBouncePromptComponent', () => { await TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], declarations: [MessageBouncePromptComponent, ModalComponent], - }).compileComponents(); + }) + .overrideComponent(MessageBouncePromptComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/projects/stream-chat-angular/src/lib/message-bounce-prompt/message-bounce-prompt.component.ts b/projects/stream-chat-angular/src/lib/message-bounce-prompt/message-bounce-prompt.component.ts index d20923b3..8382d9a7 100644 --- a/projects/stream-chat-angular/src/lib/message-bounce-prompt/message-bounce-prompt.component.ts +++ b/projects/stream-chat-angular/src/lib/message-bounce-prompt/message-bounce-prompt.component.ts @@ -1,4 +1,12 @@ -import { Component, HostBinding, OnDestroy } from '@angular/core'; +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + HostBinding, + OnDestroy, + OnInit, +} from '@angular/core'; import { ChannelService } from '../channel.service'; import { CustomTemplatesService } from '../custom-templates.service'; import { Subscription } from 'rxjs'; @@ -14,18 +22,25 @@ import { StreamMessage } from '../types'; selector: 'stream-message-bounce-prompt', templateUrl: './message-bounce-prompt.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MessageBouncePromptComponent implements OnDestroy { +export class MessageBouncePromptComponent + implements OnDestroy, OnInit, AfterViewInit +{ @HostBinding() class = 'str-chat__message-bounce-prompt'; isModalOpen = false; message?: StreamMessage; private subscriptions: Subscription[] = []; + private isViewInitialized = false; constructor( private channelService: ChannelService, readonly customTemplatesService: CustomTemplatesService, private messageActionsService: MessageActionsService, - ) { + private cdRef: ChangeDetectorRef, + ) {} + + ngOnInit(): void { this.subscriptions.push( this.channelService.bouncedMessage$.subscribe((m) => { if (m !== this.message) { @@ -33,11 +48,18 @@ export class MessageBouncePromptComponent implements OnDestroy { if (this.message) { this.isModalOpen = true; } + if (this.isViewInitialized) { + this.cdRef.markForCheck(); + } } }), ); } + ngAfterViewInit(): void { + this.isViewInitialized = true; + } + messageBounceModalOpenChanged = (isOpen: boolean) => { this.isModalOpen = isOpen; if (!isOpen) { diff --git a/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.spec.ts b/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.spec.ts index 5babf101..dc9c61cc 100644 --- a/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.spec.ts @@ -1,4 +1,4 @@ -import { SimpleChange } from '@angular/core'; +import { SimpleChange, ChangeDetectionStrategy } from '@angular/core'; import { ComponentFixture, fakeAsync, @@ -50,7 +50,11 @@ describe('AutocompleteTextareaComponent', () => { useValue: { emojiInput$ }, }, ], - }).compileComponents(); + }) + .overrideComponent(AutocompleteTextareaComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); fixture = TestBed.createComponent(AutocompleteTextareaComponent); component = fixture.componentInstance; nativeElement = fixture.nativeElement as HTMLElement; diff --git a/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.ts b/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.ts index 17484205..8de78cbb 100644 --- a/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.ts +++ b/projects/stream-chat-angular/src/lib/message-input/autocomplete-textarea/autocomplete-textarea.component.ts @@ -1,5 +1,6 @@ import { AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, @@ -7,6 +8,7 @@ import { HostBinding, Input, OnChanges, + OnInit, Output, SimpleChanges, TemplateRef, @@ -24,7 +26,7 @@ import { UserResponse } from 'stream-chat'; import { ChannelService } from '../../channel.service'; import { TextareaInterface } from '../textarea.interface'; import { ChatClientService } from '../../chat-client.service'; -import { debounceTime, filter } from 'rxjs/operators'; +import { debounceTime, filter, pairwise, startWith } from 'rxjs/operators'; import { TransliterationService } from '../../transliteration.service'; import { EmojiInputService } from '../emoji-input.service'; import { CustomTemplatesService } from '../../custom-templates.service'; @@ -37,9 +39,10 @@ import { MessageInputConfigService } from '../message-input-config.service'; selector: 'stream-autocomplete-textarea', templateUrl: './autocomplete-textarea.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AutocompleteTextareaComponent - implements TextareaInterface, OnChanges, AfterViewInit + implements TextareaInterface, OnChanges, OnInit, AfterViewInit { @HostBinding() class = 'str-chat__textarea str-chat__message-textarea-angular-host'; @@ -153,19 +156,83 @@ export class AutocompleteTextareaComponent void this.updateCustomAutocompleteOptions(searchTerm); } }); + + this.autocompleteConfig.mentions = [ + this.userMentionConfig, + this.slashCommandConfig, + ]; + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.areMentionsEnabled) { + this.autocompleteConfig.mentions = + this.autocompleteConfig?.mentions?.filter((c) => { + if (c !== this.userMentionConfig) { + return true; + } else { + return this.areMentionsEnabled; + } + }) ?? []; + this.autocompleteConfig = { ...this.autocompleteConfig }; + } + if (changes.mentionScope) { + void this.updateMentionOptions(this.searchTerm$.getValue()); + } + if (changes.value && !this.value && this.messageInput) { + this.messageInput.nativeElement.style.height = 'auto'; + this.chatClientService?.chatClient?.logger?.( + 'info', + '[Autocomplete textarea] Value reset, adjusting textarea height to auto', + ); + this.updateMentionedUsersFromText(); + } else if ( + changes.value && + this.value && + this.messageInput && + this.isViewInited + ) { + this.chatClientService?.chatClient?.logger?.( + 'info', + '[Autocomplete textarea] Value changed', + ); + setTimeout(() => { + if (this.messageInput.nativeElement.scrollHeight > 0) { + this.adjustTextareaHeight(); + } + }, 0); + } + this.cdRef.markForCheck(); + } + + ngOnInit(): void { this.subscriptions.push( - this.channelService.activeChannel$.subscribe((channel) => { - const commands = channel?.getConfig()?.commands || []; - this.slashCommandConfig.items = commands.map((c) => ({ - ...c, - [this.autocompleteKey]: c.name, - type: 'command', - })); - this.mentionedUsers = []; - this.userMentions.next([...this.mentionedUsers]); - void this.updateMentionOptions(this.searchTerm$.getValue()); - void this.updateCustomAutocompleteOptions(this.searchTerm$.getValue()); - }), + this.channelService.activeChannel$ + .pipe(startWith(undefined), pairwise()) + .subscribe(([prevChannel, channel]) => { + const config = channel?.disconnected + ? undefined + : channel?.getConfig(); + const prevConfig = prevChannel?.disconnected + ? undefined + : prevChannel?.getConfig(); + if (config !== prevConfig) { + const commands = config?.commands || []; + this.slashCommandConfig.items = commands.map((c) => ({ + ...c, + [this.autocompleteKey]: c.name, + type: 'command', + })); + this.mentionedUsers = []; + this.userMentions.next([...this.mentionedUsers]); + void this.updateMentionOptions(this.searchTerm$.getValue()); + void this.updateCustomAutocompleteOptions( + this.searchTerm$.getValue(), + ); + if (this.isViewInited) { + this.cdRef.markForCheck(); + } + } + }), ); this.subscriptions.push( this.emojiInputService.emojiInput$.subscribe((emoji) => { @@ -177,22 +244,31 @@ export class AutocompleteTextareaComponent this.messageInput.nativeElement.selectionEnd = selectionStart! + emoji.length; this.inputChanged(); + if (this.isViewInited) { + this.cdRef.markForCheck(); + } }), ); this.subscriptions.push( this.customTemplatesService.mentionAutocompleteItemTemplate$.subscribe( - (template) => (this.mentionAutocompleteItemTemplate = template), + (template) => { + this.mentionAutocompleteItemTemplate = template; + if (this.isViewInited) { + this.cdRef.markForCheck(); + } + }, ), ); this.subscriptions.push( this.customTemplatesService.commandAutocompleteItemTemplate$.subscribe( - (template) => (this.commandAutocompleteItemTemplate = template), + (template) => { + this.commandAutocompleteItemTemplate = template; + if (this.isViewInited) { + this.cdRef.markForCheck(); + } + }, ), ); - this.autocompleteConfig.mentions = [ - this.userMentionConfig, - this.slashCommandConfig, - ]; this.subscriptions.push( this.messageInputConfigService.customAutocompletes$.subscribe( (customConfigs) => { @@ -231,51 +307,14 @@ export class AutocompleteTextareaComponent ...transformedCustomConfigs, ]; this.autocompleteConfig = { ...this.autocompleteConfig }; + if (this.isViewInited) { + this.cdRef.markForCheck(); + } }, ), ); } - ngOnChanges(changes: SimpleChanges): void { - if (changes.areMentionsEnabled) { - this.autocompleteConfig.mentions = - this.autocompleteConfig?.mentions?.filter((c) => { - if (c !== this.userMentionConfig) { - return true; - } else { - return this.areMentionsEnabled; - } - }) ?? []; - this.autocompleteConfig = { ...this.autocompleteConfig }; - } - if (changes.mentionScope) { - void this.updateMentionOptions(this.searchTerm$.getValue()); - } - if (changes.value && !this.value && this.messageInput) { - this.messageInput.nativeElement.style.height = 'auto'; - this.chatClientService?.chatClient?.logger?.( - 'info', - '[Autocomplete textarea] Value reset, adjusting textarea height to auto', - ); - this.updateMentionedUsersFromText(); - } else if ( - changes.value && - this.value && - this.messageInput && - this.isViewInited - ) { - this.chatClientService?.chatClient?.logger?.( - 'info', - '[Autocomplete textarea] Value changed', - ); - setTimeout(() => { - if (this.messageInput.nativeElement.scrollHeight > 0) { - this.adjustTextareaHeight(); - } - }, 0); - } - } - ngAfterViewInit(): void { this.isViewInited = true; this.chatClientService?.chatClient?.logger?.( @@ -348,6 +387,7 @@ export class AutocompleteTextareaComponent ); this.messageInput.nativeElement.style.height = ''; this.messageInput.nativeElement.style.height = `${this.messageInput.nativeElement.scrollHeight}px`; + this.cdRef.markForCheck(); } private transliterate(s: string) { @@ -384,7 +424,7 @@ export class AutocompleteTextareaComponent ...(this.autocompleteConfig?.mentions ?? []), ]; this.autocompleteConfig = { ...this.autocompleteConfig }; - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } private updateMentionedUsersFromText() { diff --git a/projects/stream-chat-angular/src/lib/message-input/message-input.component.spec.ts b/projects/stream-chat-angular/src/lib/message-input/message-input.component.spec.ts index d3232b76..8ad86209 100644 --- a/projects/stream-chat-angular/src/lib/message-input/message-input.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/message-input/message-input.component.spec.ts @@ -1,4 +1,8 @@ -import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange } from '@angular/core'; +import { + CUSTOM_ELEMENTS_SCHEMA, + SimpleChange, + ChangeDetectionStrategy, +} from '@angular/core'; import { ComponentFixture, discardPeriodicTasks, @@ -156,6 +160,9 @@ describe('MessageInputComponent', () => { ], schemas: [CUSTOM_ELEMENTS_SCHEMA], }); + TestBed.overrideComponent(MessageInputComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }); fixture = TestBed.createComponent(MessageInputComponent); component = fixture.componentInstance; spyOn(component, 'messageSent').and.callThrough(); diff --git a/projects/stream-chat-angular/src/lib/message-input/message-input.component.ts b/projects/stream-chat-angular/src/lib/message-input/message-input.component.ts index 76af084c..282f352d 100644 --- a/projects/stream-chat-angular/src/lib/message-input/message-input.component.ts +++ b/projects/stream-chat-angular/src/lib/message-input/message-input.component.ts @@ -1,5 +1,6 @@ import { AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentFactoryResolver, @@ -54,6 +55,7 @@ import { AudioRecorderService } from '../voice-recorder/audio-recorder.service'; templateUrl: './message-input.component.html', styles: [], providers: [AttachmentService, EmojiInputService, VoiceRecorderService], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MessageInputComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit @@ -175,6 +177,9 @@ export class MessageInputComponent if (counter === 0 && this.hideNotification) { this.hideNotification(); this.hideNotification = undefined; + if (this.isViewInited) { + this.cdRef.markForCheck(); + } } }, ), @@ -194,6 +199,9 @@ export class MessageInputComponent this.channel = channel; this.setCanSendMessages(); } + if (this.isViewInited) { + this.cdRef.markForCheck(); + } }), ); this.subscriptions.push( @@ -205,6 +213,9 @@ export class MessageInputComponent (this.mode === 'main' && !isThreadReply) ) { this.quotedMessage = m; + if (this.isViewInited) { + this.cdRef.markForCheck(); + } } }), ); @@ -212,6 +223,9 @@ export class MessageInputComponent this.messageActionsService.messageToEdit$.subscribe((message) => { this.messageToEdit = message; this.checkIfInEditMode(); + if (this.isViewInited) { + this.cdRef.markForCheck(); + } }), ); this.attachmentUploads$ = this.attachmentService.attachmentUploads$; @@ -232,7 +246,12 @@ export class MessageInputComponent ); this.subscriptions.push( this.voiceRecorderService.isRecorderVisible$.subscribe((isVisible) => { - this.isVoiceRecording = isVisible; + if (isVisible !== this.isVoiceRecording) { + this.isVoiceRecording = isVisible; + if (this.isViewInited) { + this.cdRef.markForCheck(); + } + } }), ); @@ -263,8 +282,14 @@ export class MessageInputComponent (channel?.data?.own_capabilities as string[]).includes('slow-mode') ) { this.startCooldown(cooldown); + if (this.isViewInited) { + this.cdRef.markForCheck(); + } } else if (this.isCooldownInProgress) { this.stopCooldown(); + if (this.isViewInited) { + this.cdRef.markForCheck(); + } } }), ); @@ -272,6 +297,9 @@ export class MessageInputComponent this.voiceRecorderService.recording$.subscribe((recording) => { if (recording) { void this.voiceRecordingReady(recording); + if (this.isViewInited) { + this.cdRef.markForCheck(); + } } }), ); @@ -320,14 +348,14 @@ export class MessageInputComponent this.subscriptions.push( this.customTemplatesService.emojiPickerTemplate$.subscribe((template) => { this.emojiPickerTemplate = template; - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); }), ); this.subscriptions.push( this.customTemplatesService.attachmentPreviewListTemplate$.subscribe( (template) => { this.attachmentPreviewListTemplate = template; - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); }, ), ); @@ -335,7 +363,7 @@ export class MessageInputComponent this.customTemplatesService.customAttachmentUploadTemplate$.subscribe( (template) => { this.customAttachmentUploadTemplate = template; - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); }, ), ); @@ -617,7 +645,7 @@ export class MessageInputComponent this.message = undefined; this.messageToUpdateChanged(); if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } } if ( @@ -628,7 +656,7 @@ export class MessageInputComponent this.message = this.messageToEdit; this.messageToUpdateChanged(); if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } } } diff --git a/projects/stream-chat-angular/src/lib/message-input/textarea/textarea.component.spec.ts b/projects/stream-chat-angular/src/lib/message-input/textarea/textarea.component.spec.ts index 9edc142f..d93f30a7 100644 --- a/projects/stream-chat-angular/src/lib/message-input/textarea/textarea.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/message-input/textarea/textarea.component.spec.ts @@ -1,4 +1,4 @@ -import { SimpleChange } from '@angular/core'; +import { SimpleChange, ChangeDetectionStrategy } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; import { Subject } from 'rxjs'; @@ -24,7 +24,11 @@ describe('TextareaComponent', () => { useValue: { emojiInput$ }, }, ], - }).compileComponents(); + }) + .overrideComponent(TextareaComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/projects/stream-chat-angular/src/lib/message-input/textarea/textarea.component.ts b/projects/stream-chat-angular/src/lib/message-input/textarea/textarea.component.ts index 9b0a64c0..721bc1dc 100644 --- a/projects/stream-chat-angular/src/lib/message-input/textarea/textarea.component.ts +++ b/projects/stream-chat-angular/src/lib/message-input/textarea/textarea.component.ts @@ -1,5 +1,7 @@ import { AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, Component, ElementRef, EventEmitter, @@ -7,6 +9,7 @@ import { Input, OnChanges, OnDestroy, + OnInit, Output, SimpleChanges, ViewChild, @@ -23,9 +26,10 @@ import { UserResponse } from 'stream-chat'; selector: 'stream-textarea', templateUrl: './textarea.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TextareaComponent - implements TextareaInterface, OnChanges, OnDestroy, AfterViewInit + implements TextareaInterface, OnChanges, OnInit, OnDestroy, AfterViewInit { @HostBinding() class = 'str-chat__textarea str-chat__message-textarea-angular-host'; @@ -64,20 +68,10 @@ export class TextareaComponent private subscriptions: Subscription[] = []; private isViewInited = false; - constructor(private emojiInputService: EmojiInputService) { - this.subscriptions.push( - this.emojiInputService.emojiInput$.subscribe((emoji) => { - this.messageInput.nativeElement.focus(); - const { selectionStart } = this.messageInput.nativeElement; - this.messageInput.nativeElement.setRangeText(emoji); - this.messageInput.nativeElement.selectionStart = - selectionStart! + emoji.length; - this.messageInput.nativeElement.selectionEnd = - selectionStart! + emoji.length; - this.inputChanged(); - }), - ); - } + constructor( + private emojiInputService: EmojiInputService, + private cdRef: ChangeDetectorRef, + ) {} ngOnChanges(changes: SimpleChanges): void { if (changes.value && !this.value && this.messageInput) { @@ -94,6 +88,25 @@ export class TextareaComponent } }, 0); } + this.cdRef.markForCheck(); + } + + ngOnInit(): void { + this.subscriptions.push( + this.emojiInputService.emojiInput$.subscribe((emoji) => { + this.messageInput.nativeElement.focus(); + const { selectionStart } = this.messageInput.nativeElement; + this.messageInput.nativeElement.setRangeText(emoji); + this.messageInput.nativeElement.selectionStart = + selectionStart! + emoji.length; + this.messageInput.nativeElement.selectionEnd = + selectionStart! + emoji.length; + this.inputChanged(); + if (this.isViewInited) { + this.cdRef.markForCheck(); + } + }), + ); } ngAfterViewInit(): void { diff --git a/projects/stream-chat-angular/src/lib/message-list/message-list.component.spec.ts b/projects/stream-chat-angular/src/lib/message-list/message-list.component.spec.ts index 6e446752..752fdc95 100644 --- a/projects/stream-chat-angular/src/lib/message-list/message-list.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/message-list/message-list.component.spec.ts @@ -1,4 +1,8 @@ -import { ChangeDetectorRef, SimpleChange } from '@angular/core'; +import { + ChangeDetectorRef, + SimpleChange, + ChangeDetectionStrategy, +} from '@angular/core'; import { ComponentFixture, fakeAsync, @@ -66,6 +70,8 @@ describe('MessageListComponent', () => { }, }, ], + }).overrideComponent(MessageListComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, }); fixture = TestBed.createComponent(MessageListComponent); component = fixture.componentInstance; @@ -1097,6 +1103,7 @@ describe('MessageListComponent', () => { messages[messages.length - 2].id; channelServiceMock.activeChannel$.next(channel); channelServiceMock.activeChannelMessages$.next(messages); + fixture.detectChanges(); tick(); expect(component.lastReadMessageId).toBe(messages[messages.length - 2].id); diff --git a/projects/stream-chat-angular/src/lib/message-list/message-list.component.ts b/projects/stream-chat-angular/src/lib/message-list/message-list.component.ts index c0ae3ea8..512ec482 100644 --- a/projects/stream-chat-angular/src/lib/message-list/message-list.component.ts +++ b/projects/stream-chat-angular/src/lib/message-list/message-list.component.ts @@ -221,7 +221,7 @@ export class MessageListComponent this.setMessages$(); this.channelId = channel?.id; if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } } if (this.mode === 'main') { @@ -261,21 +261,21 @@ export class MessageListComponent ) { this.isUnreadNotificationVisible = true; if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } } }, 100); } } if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } } } else if (this.lastReadMessageId) { this.lastReadMessageId = undefined; this.unreadCount = 0; if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } } const capabilites = channel?.data?.own_capabilities as string[]; @@ -286,7 +286,7 @@ export class MessageListComponent if (capabilitesString !== enabledActionsString) { this.enabledMessageActions = capabilites || []; if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } } this.newMessageSubscription?.unsubscribe(); @@ -320,7 +320,7 @@ export class MessageListComponent } this.parentMessage = message; if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } }), ); @@ -331,7 +331,7 @@ export class MessageListComponent } this.messageTemplate = template; if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } }), ); @@ -343,7 +343,7 @@ export class MessageListComponent } this.customDateSeparatorTemplate = template; if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } }, ), @@ -356,7 +356,7 @@ export class MessageListComponent } this.customnewMessagesIndicatorTemplate = template; if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } }, ), @@ -369,7 +369,7 @@ export class MessageListComponent } this.customnewMessagesNotificationTemplate = template; if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } }, ), @@ -382,7 +382,7 @@ export class MessageListComponent } this.typingIndicatorTemplate = template; if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } }, ), @@ -393,7 +393,7 @@ export class MessageListComponent const isChanged = this.emptyMainMessageListTemplate !== template; this.emptyMainMessageListTemplate = template || null; if (isChanged && this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } }, ), @@ -404,7 +404,7 @@ export class MessageListComponent const isChanged = this.emptyThreadMessageListTemplate !== template; this.emptyThreadMessageListTemplate = template || null; if (isChanged && this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } }, ), @@ -682,7 +682,7 @@ export class MessageListComponent if (mappedState !== this.loadingState) { this.loadingState = mappedState; if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } } }, @@ -844,7 +844,7 @@ export class MessageListComponent this.firstUnreadMessageId = undefined; this.isJumpingToLatestUnreadMessage = false; this.jumpToMessageTimeouts = []; - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); }, 1000), ); } else { @@ -882,7 +882,7 @@ export class MessageListComponent } if (shouldDetectChanges && this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } } diff --git a/projects/stream-chat-angular/src/lib/message-reactions-selector/message-reactions-selector.component.spec.ts b/projects/stream-chat-angular/src/lib/message-reactions-selector/message-reactions-selector.component.spec.ts index 91be7c97..b5b912e1 100644 --- a/projects/stream-chat-angular/src/lib/message-reactions-selector/message-reactions-selector.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/message-reactions-selector/message-reactions-selector.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ChangeDetectionStrategy } from '@angular/core'; import { MessageReactionType } from '../types'; import { ReactionResponse } from 'stream-chat'; @@ -43,7 +44,11 @@ describe('MessageReactionsSelectorComponent', () => { { provide: ChannelService, useValue: channelServiceMock }, { provide: MessageReactionsService, useValue: reactionsServiceMock }, ], - }).compileComponents(); + }) + .overrideComponent(MessageReactionsSelectorComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/projects/stream-chat-angular/src/lib/message-reactions-selector/message-reactions-selector.component.ts b/projects/stream-chat-angular/src/lib/message-reactions-selector/message-reactions-selector.component.ts index 8f7ae18e..8de6a275 100644 --- a/projects/stream-chat-angular/src/lib/message-reactions-selector/message-reactions-selector.component.ts +++ b/projects/stream-chat-angular/src/lib/message-reactions-selector/message-reactions-selector.component.ts @@ -1,5 +1,6 @@ import { AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, @@ -19,6 +20,7 @@ import { Subscription } from 'rxjs'; selector: 'stream-message-reactions-selector', templateUrl: './message-reactions-selector.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MessageReactionsSelectorComponent implements OnInit, OnDestroy, AfterViewInit @@ -45,7 +47,7 @@ export class MessageReactionsSelectorComponent this.messageReactionsService.reactions$.subscribe((reactions) => { this.reactionOptions = Object.keys(reactions); if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } }), ); diff --git a/projects/stream-chat-angular/src/lib/message-reactions/message-reactions.component.spec.ts b/projects/stream-chat-angular/src/lib/message-reactions/message-reactions.component.spec.ts index a3779af1..72e2462a 100644 --- a/projects/stream-chat-angular/src/lib/message-reactions/message-reactions.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/message-reactions/message-reactions.component.spec.ts @@ -8,7 +8,7 @@ import { QueryReactionsAPIResponse, ReactionResponse } from 'stream-chat'; import { AvatarComponent } from '../avatar/avatar.component'; import { MessageReactionsComponent } from './message-reactions.component'; -import { SimpleChange } from '@angular/core'; +import { SimpleChange, ChangeDetectionStrategy } from '@angular/core'; import { AvatarPlaceholderComponent } from '../avatar-placeholder/avatar-placeholder.component'; import { MessageReactionsService } from '../message-reactions.service'; import { ModalComponent } from '../modal/modal.component'; @@ -60,7 +60,11 @@ describe('MessageReactionsComponent', () => { { provide: MessageReactionsService, useValue: reactionsServiceMock }, ], imports: [TranslateModule.forRoot()], - }).compileComponents(); + }) + .overrideComponent(MessageReactionsComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/projects/stream-chat-angular/src/lib/message-reactions/message-reactions.component.ts b/projects/stream-chat-angular/src/lib/message-reactions/message-reactions.component.ts index 7d7cd494..a58adcce 100644 --- a/projects/stream-chat-angular/src/lib/message-reactions/message-reactions.component.ts +++ b/projects/stream-chat-angular/src/lib/message-reactions/message-reactions.component.ts @@ -1,14 +1,13 @@ import { AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, - ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, - ViewChild, } from '@angular/core'; import { ReactionGroupResponse, @@ -26,6 +25,7 @@ import { Subscription } from 'rxjs'; @Component({ selector: 'stream-message-reactions', templateUrl: './message-reactions.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MessageReactionsComponent implements OnChanges, OnInit, AfterViewInit, OnDestroy @@ -55,9 +55,6 @@ export class MessageReactionsComponent * List of the user's own reactions of a [message](/chat/docs/sdk/angular/v6-rc/types/stream-message/), used to display the users of a reaction type. */ @Input() ownReactions: ReactionResponse[] = []; - @ViewChild('selectorContainer') private selectorContainer: - | ElementRef - | undefined; selectedReactionType: string | undefined; isLoading = true; reactions: ReactionResponse[] = []; @@ -99,7 +96,7 @@ export class MessageReactionsComponent this.reactionOptions = Object.keys(reactions); this.setExistingReactions(); if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } }), ); @@ -162,7 +159,7 @@ export class MessageReactionsComponent this.isLoading = false; } if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } } diff --git a/projects/stream-chat-angular/src/lib/message-text/message-text.component.spec.ts b/projects/stream-chat-angular/src/lib/message-text/message-text.component.spec.ts index ace36f3b..291755df 100644 --- a/projects/stream-chat-angular/src/lib/message-text/message-text.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/message-text/message-text.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MessageTextComponent } from './message-text.component'; import { StreamMessage } from '../types'; -import { SimpleChange } from '@angular/core'; +import { SimpleChange, ChangeDetectionStrategy } from '@angular/core'; import { MessageService } from '../message.service'; import { generateMockMessages } from '../mocks'; @@ -14,7 +14,11 @@ describe('MessageTextComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [MessageTextComponent], - }).compileComponents(); + }) + .overrideComponent(MessageTextComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); fixture = TestBed.createComponent(MessageTextComponent); component = fixture.componentInstance; diff --git a/projects/stream-chat-angular/src/lib/message-text/message-text.component.ts b/projects/stream-chat-angular/src/lib/message-text/message-text.component.ts index 75486630..e72f211f 100644 --- a/projects/stream-chat-angular/src/lib/message-text/message-text.component.ts +++ b/projects/stream-chat-angular/src/lib/message-text/message-text.component.ts @@ -1,4 +1,10 @@ -import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + Input, + OnChanges, + SimpleChanges, +} from '@angular/core'; import { MentionTemplateContext, StreamMessage } from '../types'; import { UserResponse } from 'stream-chat'; import emojiRegex from 'emoji-regex'; @@ -18,6 +24,7 @@ type MessagePart = { selector: 'stream-message-text', templateUrl: './message-text.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MessageTextComponent implements OnChanges { /** diff --git a/projects/stream-chat-angular/src/lib/message/message.component.ts b/projects/stream-chat-angular/src/lib/message/message.component.ts index df02007f..d341831b 100644 --- a/projects/stream-chat-angular/src/lib/message/message.component.ts +++ b/projects/stream-chat-angular/src/lib/message/message.component.ts @@ -238,7 +238,7 @@ export class MessageComponent this.setIsSentByCurrentUser(); this.setLastReadUser(); if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } } }), @@ -254,7 +254,7 @@ export class MessageComponent if (numberOfEnabledActions !== this.visibleMessageActionsCount) { this.visibleMessageActionsCount = numberOfEnabledActions; if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } } } @@ -562,7 +562,7 @@ export class MessageComponent ); } if (this.isViewInited) { - this.cdRef.detectChanges(); + this.cdRef.markForCheck(); } this.showMessageMenuTimeout = undefined; }); diff --git a/projects/stream-chat-angular/src/lib/mocks/index.ts b/projects/stream-chat-angular/src/lib/mocks/index.ts index b75dd31a..2c8bd201 100644 --- a/projects/stream-chat-angular/src/lib/mocks/index.ts +++ b/projects/stream-chat-angular/src/lib/mocks/index.ts @@ -225,7 +225,7 @@ export type MockChannelService = { hasMoreChannels$: Subject; channels$: Subject; activeChannelMessages$: BehaviorSubject; - activeChannel$: Subject; + activeChannel$: Subject; activeThreadMessages$: BehaviorSubject; activeParentMessageId$: BehaviorSubject; activeParentMessage$: BehaviorSubject; @@ -267,7 +267,9 @@ export const mockChannelService = (): MockChannelService => { activeChannel.state.messages = messages; activeChannel.state.latestMessages = messages; const activeChannelMessages = messages; - const activeChannel$ = new BehaviorSubject(activeChannel); + const activeChannel$ = new BehaviorSubject( + activeChannel, + ); const channels$ = new BehaviorSubject(undefined); const hasMoreChannels$ = new ReplaySubject(1); const jumpToMessage$ = new BehaviorSubject<{ diff --git a/projects/stream-chat-angular/src/lib/modal/modal.component.spec.ts b/projects/stream-chat-angular/src/lib/modal/modal.component.spec.ts index 595b0aec..dcb46624 100644 --- a/projects/stream-chat-angular/src/lib/modal/modal.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/modal/modal.component.spec.ts @@ -1,4 +1,4 @@ -import { SimpleChange } from '@angular/core'; +import { SimpleChange, ChangeDetectionStrategy } from '@angular/core'; import { ComponentFixture, fakeAsync, @@ -17,7 +17,11 @@ describe('ModalComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ModalComponent], - }).compileComponents(); + }) + .overrideComponent(ModalComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/projects/stream-chat-angular/src/lib/modal/modal.component.ts b/projects/stream-chat-angular/src/lib/modal/modal.component.ts index fdb256a9..557b2a7b 100644 --- a/projects/stream-chat-angular/src/lib/modal/modal.component.ts +++ b/projects/stream-chat-angular/src/lib/modal/modal.component.ts @@ -1,4 +1,5 @@ import { + ChangeDetectionStrategy, Component, ElementRef, EventEmitter, @@ -17,6 +18,7 @@ import { selector: 'stream-modal', templateUrl: './modal.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ModalComponent implements OnChanges { /** diff --git a/projects/stream-chat-angular/src/lib/notification-list/notification-list.component.spec.ts b/projects/stream-chat-angular/src/lib/notification-list/notification-list.component.spec.ts index a3926326..b8bb1fd6 100644 --- a/projects/stream-chat-angular/src/lib/notification-list/notification-list.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/notification-list/notification-list.component.spec.ts @@ -4,6 +4,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { NotificationService } from '../notification.service'; import { NotificationComponent } from '../notification/notification.component'; import { ThemeService } from '../theme.service'; +import { ChangeDetectionStrategy } from '@angular/core'; import { NotificationListComponent } from './notification-list.component'; @@ -18,7 +19,11 @@ describe('NotificationListComponent', () => { providers: [ThemeService], imports: [TranslateModule.forRoot()], declarations: [NotificationListComponent, NotificationComponent], - }).compileComponents(); + }) + .overrideComponent(NotificationListComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/projects/stream-chat-angular/src/lib/notification-list/notification-list.component.ts b/projects/stream-chat-angular/src/lib/notification-list/notification-list.component.ts index 4dc6b9e5..0d1a65b9 100644 --- a/projects/stream-chat-angular/src/lib/notification-list/notification-list.component.ts +++ b/projects/stream-chat-angular/src/lib/notification-list/notification-list.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Observable } from 'rxjs'; import { CustomTemplatesService } from '../custom-templates.service'; import { NotificationService } from '../notification.service'; @@ -12,6 +12,7 @@ import { NotificationPayload } from '../types'; selector: 'stream-notification-list', templateUrl: './notification-list.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class NotificationListComponent { notifications$: Observable; diff --git a/projects/stream-chat-angular/src/lib/notification/notification.component.spec.ts b/projects/stream-chat-angular/src/lib/notification/notification.component.spec.ts index 8cf4ba6e..21ff6db0 100644 --- a/projects/stream-chat-angular/src/lib/notification/notification.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/notification/notification.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ChangeDetectionStrategy } from '@angular/core'; import { NotificationComponent } from './notification.component'; @@ -11,7 +12,11 @@ describe('NotificationComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [NotificationComponent], - }).compileComponents(); + }) + .overrideComponent(NotificationComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/projects/stream-chat-angular/src/lib/notification/notification.component.ts b/projects/stream-chat-angular/src/lib/notification/notification.component.ts index b6a1d36e..70d3a2f5 100644 --- a/projects/stream-chat-angular/src/lib/notification/notification.component.ts +++ b/projects/stream-chat-angular/src/lib/notification/notification.component.ts @@ -1,4 +1,9 @@ -import { Component, Input, TemplateRef } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + Input, + TemplateRef, +} from '@angular/core'; import { NotificationType } from '../types'; /** @@ -8,6 +13,7 @@ import { NotificationType } from '../types'; selector: 'stream-notification', templateUrl: './notification.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class NotificationComponent { /** diff --git a/projects/stream-chat-angular/src/lib/paginated-list/paginated-list.component.spec.ts b/projects/stream-chat-angular/src/lib/paginated-list/paginated-list.component.spec.ts index 0f8f818f..d1e5f1cf 100644 --- a/projects/stream-chat-angular/src/lib/paginated-list/paginated-list.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/paginated-list/paginated-list.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; import { PaginatedListComponent } from './paginated-list.component'; -import { Component } from '@angular/core'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; describe('PaginatedListComponentt', () => { let hostComponent: TestHostComponent; @@ -38,7 +38,11 @@ describe('PaginatedListComponentt', () => { await TestBed.configureTestingModule({ declarations: [PaginatedListComponent, TestHostComponent], imports: [TranslateModule.forRoot()], - }).compileComponents(); + }) + .overrideComponent(PaginatedListComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); hostFixture = TestBed.createComponent(TestHostComponent); hostComponent = hostFixture.componentInstance; diff --git a/projects/stream-chat-angular/src/lib/paginated-list/paginated-list.component.ts b/projects/stream-chat-angular/src/lib/paginated-list/paginated-list.component.ts index 049a6100..a15642db 100644 --- a/projects/stream-chat-angular/src/lib/paginated-list/paginated-list.component.ts +++ b/projects/stream-chat-angular/src/lib/paginated-list/paginated-list.component.ts @@ -1,5 +1,6 @@ import { AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, @@ -20,6 +21,7 @@ import { selector: 'stream-paginated-list', templateUrl: './paginated-list.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class PaginatedListComponent implements AfterViewInit { /** diff --git a/projects/stream-chat-angular/src/lib/thread/thread.component.spec.ts b/projects/stream-chat-angular/src/lib/thread/thread.component.spec.ts index deaebc61..baa53345 100644 --- a/projects/stream-chat-angular/src/lib/thread/thread.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/thread/thread.component.spec.ts @@ -10,6 +10,7 @@ import { MockChannelService, mockMessage, } from '../mocks'; +import { ChangeDetectionStrategy } from '@angular/core'; import { ThreadComponent } from './thread.component'; @@ -31,7 +32,11 @@ describe('ThreadComponent', () => { useValue: { chatClient: { user: { id: 'userid' } } }, }, ], - }).compileComponents(); + }) + .overrideComponent(ThreadComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/projects/stream-chat-angular/src/lib/thread/thread.component.ts b/projects/stream-chat-angular/src/lib/thread/thread.component.ts index 3632f907..1134681c 100644 --- a/projects/stream-chat-angular/src/lib/thread/thread.component.ts +++ b/projects/stream-chat-angular/src/lib/thread/thread.component.ts @@ -1,11 +1,19 @@ -import { Component, HostBinding, OnDestroy } from '@angular/core'; +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + HostBinding, + OnDestroy, + OnInit, +} from '@angular/core'; import { Subscription } from 'rxjs'; -import { Channel } from 'stream-chat'; import { ChatClientService } from '../chat-client.service'; import { ChannelService } from '../channel.service'; import { CustomTemplatesService } from '../custom-templates.service'; import { getChannelDisplayText } from '../get-channel-display-text'; import { StreamMessage, ThreadHeaderContext } from '../types'; +import { Channel } from 'stream-chat'; /** * The `Thread` component represents a [message thread](/chat/docs/javascript/threads/), it is a container component that displays a thread with a header, [`MessageList`](/chat/docs/sdk/angular/v6-rc/components/MessageListComponent) and [`MessageInput`](/chat/docs/sdk/angular/v6-rc/components/MessageInputComponent/) components. @@ -14,29 +22,46 @@ import { StreamMessage, ThreadHeaderContext } from '../types'; selector: 'stream-thread', templateUrl: './thread.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ThreadComponent implements OnDestroy { +export class ThreadComponent implements OnInit, AfterViewInit, OnDestroy { @HostBinding('class') private class = 'str-chat__thread'; parentMessage: StreamMessage | undefined; - channel: Channel | undefined; + channelName = ''; private subscriptions: Subscription[] = []; + private isViewInitialized = false; constructor( public customTemplatesService: CustomTemplatesService, private channelService: ChannelService, private chatClientService: ChatClientService, - ) { + private cdRef: ChangeDetectorRef, + ) {} + + ngOnInit(): void { this.subscriptions.push( - this.channelService.activeParentMessage$.subscribe( - (parentMessage) => (this.parentMessage = parentMessage), - ), + this.channelService.activeParentMessage$.subscribe((parentMessage) => { + this.parentMessage = parentMessage; + if (this.isViewInitialized) { + this.cdRef.markForCheck(); + } + }), ); this.subscriptions.push( - this.channelService.activeChannel$.subscribe( - (channel) => (this.channel = channel), - ), + this.channelService.activeChannel$.subscribe((channel) => { + const channelName = this.getChannelName(channel); + if (channelName !== this.channelName) { + this.channelName = channelName; + if (this.isViewInitialized) { + this.cdRef.markForCheck(); + } + } + }), ); } + ngAfterViewInit(): void { + this.isViewInitialized = true; + } ngOnDestroy(): void { this.subscriptions.forEach((s) => s.unsubscribe()); @@ -53,13 +78,13 @@ export class ThreadComponent implements OnDestroy { void this.channelService.setAsActiveParentMessage(undefined); } - get channelName() { - if (!this.channel || !this.chatClientService.chatClient.user) { + getChannelName(channel: Channel | undefined) { + if (!channel || !this.chatClientService.chatClient.user) { return ''; } - return getChannelDisplayText( - this.channel, - this.chatClientService.chatClient.user, + return ( + getChannelDisplayText(channel, this.chatClientService.chatClient.user) ?? + '' ); } } diff --git a/projects/stream-chat-angular/src/lib/user-list/user-list.component.spec.ts b/projects/stream-chat-angular/src/lib/user-list/user-list.component.spec.ts index c58675f1..c6c49fe0 100644 --- a/projects/stream-chat-angular/src/lib/user-list/user-list.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/user-list/user-list.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ChangeDetectionStrategy } from '@angular/core'; import { UserListComponent } from './user-list.component'; import { PaginatedListComponent } from '../paginated-list/paginated-list.component'; @@ -16,7 +17,11 @@ describe('UserListComponent', () => { await TestBed.configureTestingModule({ imports: [StreamAvatarModule, TranslateModule.forRoot()], declarations: [UserListComponent, PaginatedListComponent], - }).compileComponents(); + }) + .overrideComponent(UserListComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); fixture = TestBed.createComponent(UserListComponent); component = fixture.componentInstance; diff --git a/projects/stream-chat-angular/src/lib/user-list/user-list.component.ts b/projects/stream-chat-angular/src/lib/user-list/user-list.component.ts index d09ea84c..b907816a 100644 --- a/projects/stream-chat-angular/src/lib/user-list/user-list.component.ts +++ b/projects/stream-chat-angular/src/lib/user-list/user-list.component.ts @@ -1,4 +1,10 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, +} from '@angular/core'; import { UserResponse } from 'stream-chat'; /** @@ -8,6 +14,7 @@ import { UserResponse } from 'stream-chat'; selector: 'stream-user-list', templateUrl: './user-list.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class UserListComponent { /** diff --git a/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder-wavebar/voice-recorder-wavebar.component.spec.ts b/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder-wavebar/voice-recorder-wavebar.component.spec.ts index e1c6ea6d..053fce73 100644 --- a/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder-wavebar/voice-recorder-wavebar.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder-wavebar/voice-recorder-wavebar.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ChangeDetectionStrategy } from '@angular/core'; import { VoiceRecorderWavebarComponent } from './voice-recorder-wavebar.component'; import { AudioRecorderService } from '../audio-recorder.service'; @@ -17,7 +18,11 @@ describe('VoiceRecorderWavebarComponent', () => { AmplitudeRecorderService, TranscoderService, ], - }).compileComponents(); + }) + .overrideComponent(VoiceRecorderWavebarComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); fixture = TestBed.createComponent(VoiceRecorderWavebarComponent); component = fixture.componentInstance; diff --git a/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder-wavebar/voice-recorder-wavebar.component.ts b/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder-wavebar/voice-recorder-wavebar.component.ts index 2e7b930f..41ebc44e 100644 --- a/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder-wavebar/voice-recorder-wavebar.component.ts +++ b/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder-wavebar/voice-recorder-wavebar.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core'; import { AmplitudeRecorderService } from '../amplitude-recorder.service'; import { Observable } from 'rxjs'; import { AudioRecorderService } from '../audio-recorder.service'; @@ -11,6 +11,7 @@ import { formatDuration } from '../../format-duration'; selector: 'stream-voice-recorder-wavebar', templateUrl: './voice-recorder-wavebar.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class VoiceRecorderWavebarComponent implements OnDestroy { amplitudes$: Observable; diff --git a/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder.component.spec.ts b/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder.component.spec.ts index 96c17062..3a32d9f4 100644 --- a/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ChangeDetectionStrategy } from '@angular/core'; import { VoiceRecorderComponent } from './voice-recorder.component'; import { VoiceRecorderModule } from './voice-recorder.module'; @@ -10,7 +11,11 @@ describe('VoiceRecorderComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [VoiceRecorderModule], - }).compileComponents(); + }) + .overrideComponent(VoiceRecorderComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); fixture = TestBed.createComponent(VoiceRecorderComponent); component = fixture.componentInstance; diff --git a/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder.component.ts b/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder.component.ts index 291ae5c2..af14831e 100644 --- a/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder.component.ts +++ b/projects/stream-chat-angular/src/lib/voice-recorder/voice-recorder.component.ts @@ -1,4 +1,7 @@ import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, Component, Input, OnChanges, @@ -20,8 +23,11 @@ import { VoiceRecorderService } from '../message-input/voice-recorder.service'; templateUrl: './voice-recorder.component.html', styles: [], providers: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class VoiceRecorderComponent implements OnInit, OnDestroy, OnChanges { +export class VoiceRecorderComponent + implements OnInit, AfterViewInit, OnDestroy, OnChanges +{ @Input() voiceRecorderService?: VoiceRecorderService; recordState: MediaRecordingState = MediaRecordingState.STOPPED; isLoading = false; @@ -29,8 +35,12 @@ export class VoiceRecorderComponent implements OnInit, OnDestroy, OnChanges { readonly MediaRecordingState = MediaRecordingState; private subscriptions: Subscription[] = []; private isVisibleSubscription?: Subscription; + private isViewInitialized = false; - constructor(public readonly recorder: AudioRecorderService) {} + constructor( + public readonly recorder: AudioRecorderService, + private cdRef: ChangeDetectorRef, + ) {} ngOnChanges(changes: SimpleChanges): void { if (changes.voiceRecorderService && this.voiceRecorderService) { @@ -40,6 +50,9 @@ export class VoiceRecorderComponent implements OnInit, OnDestroy, OnChanges { this.recording = undefined; this.isLoading = false; } + if (this.isViewInitialized) { + this.cdRef.markForCheck(); + } }); } else { this.isVisibleSubscription?.unsubscribe(); @@ -53,10 +66,17 @@ export class VoiceRecorderComponent implements OnInit, OnDestroy, OnChanges { if (this.recordState === MediaRecordingState.ERROR) { this.voiceRecorderService?.isRecorderVisible$.next(false); } + if (this.isViewInitialized) { + this.cdRef.markForCheck(); + } }), ); } + ngAfterViewInit(): void { + this.isViewInitialized = true; + } + ngOnDestroy(): void { this.subscriptions.forEach((s) => s.unsubscribe()); } diff --git a/projects/stream-chat-angular/src/lib/voice-recording/voice-recording-wavebar/voice-recording-wavebar.component.spec.ts b/projects/stream-chat-angular/src/lib/voice-recording/voice-recording-wavebar/voice-recording-wavebar.component.spec.ts index 0299153c..19101524 100644 --- a/projects/stream-chat-angular/src/lib/voice-recording/voice-recording-wavebar/voice-recording-wavebar.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/voice-recording/voice-recording-wavebar/voice-recording-wavebar.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ChangeDetectionStrategy } from '@angular/core'; import { VoiceRecordingWavebarComponent } from './voice-recording-wavebar.component'; @@ -9,7 +10,11 @@ describe('VoiceRecordingWavebarComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [VoiceRecordingWavebarComponent], - }).compileComponents(); + }) + .overrideComponent(VoiceRecordingWavebarComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/projects/stream-chat-angular/src/lib/voice-recording/voice-recording-wavebar/voice-recording-wavebar.component.ts b/projects/stream-chat-angular/src/lib/voice-recording/voice-recording-wavebar/voice-recording-wavebar.component.ts index 441a0410..90cf2ba1 100644 --- a/projects/stream-chat-angular/src/lib/voice-recording/voice-recording-wavebar/voice-recording-wavebar.component.ts +++ b/projects/stream-chat-angular/src/lib/voice-recording/voice-recording-wavebar/voice-recording-wavebar.component.ts @@ -1,5 +1,6 @@ import { AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, @@ -19,6 +20,7 @@ import { resampleWaveForm } from '../../wave-form-sampler'; selector: 'stream-voice-recording-wavebar', templateUrl: './voice-recording-wavebar.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class VoiceRecordingWavebarComponent implements OnChanges, OnInit, AfterViewInit diff --git a/projects/stream-chat-angular/src/lib/voice-recording/voice-recording.component.spec.ts b/projects/stream-chat-angular/src/lib/voice-recording/voice-recording.component.spec.ts index 9405cef0..5a9fae34 100644 --- a/projects/stream-chat-angular/src/lib/voice-recording/voice-recording.component.spec.ts +++ b/projects/stream-chat-angular/src/lib/voice-recording/voice-recording.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { VoiceRecordingComponent } from './voice-recording.component'; import { mockVoiceRecording } from '../mocks'; -import { SimpleChange } from '@angular/core'; +import { SimpleChange, ChangeDetectionStrategy } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; describe('VoiceRecordingComponent', () => { @@ -15,7 +15,11 @@ describe('VoiceRecordingComponent', () => { await TestBed.configureTestingModule({ declarations: [VoiceRecordingComponent], imports: [TranslateModule.forRoot()], - }).compileComponents(); + }) + .overrideComponent(VoiceRecordingComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default }, + }) + .compileComponents(); }); beforeEach(() => { diff --git a/projects/stream-chat-angular/src/lib/voice-recording/voice-recording.component.ts b/projects/stream-chat-angular/src/lib/voice-recording/voice-recording.component.ts index fe8057ea..5ec07ad5 100644 --- a/projects/stream-chat-angular/src/lib/voice-recording/voice-recording.component.ts +++ b/projects/stream-chat-angular/src/lib/voice-recording/voice-recording.component.ts @@ -1,5 +1,6 @@ import { AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, @@ -20,6 +21,7 @@ import { formatDuration } from '../format-duration'; selector: 'stream-voice-recording', templateUrl: './voice-recording.component.html', styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class VoiceRecordingComponent implements OnChanges, AfterViewInit { /** From 5e322b33e6302101451241818b742794e7d867f7 Mon Sep 17 00:00:00 2001 From: Zita Szupera Date: Wed, 19 Mar 2025 15:11:47 +0100 Subject: [PATCH 2/2] chore: enable test runs --- .github/workflows/workflow.yml | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index dc95e8ca..dee45a96 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -3,11 +3,11 @@ on: push: branches: - master - - 6.x.x-rc + - 7.x.x-beta pull_request: branches: - master - - 6.x.x-rc + - 7.x.x-beta jobs: workflow: runs-on: ubuntu-latest diff --git a/package.json b/package.json index 9625baf3..ed7a6736 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "channel": "rc" } ], - "dryRun": false, + "dryRun": true, "plugins": [ [ "@semantic-release/commit-analyzer", @@ -79,7 +79,7 @@ "@semantic-release/npm", { "pkgRoot": "./dist/stream-chat-angular", - "npmPublish": true + "npmPublish": false } ], [