Skip to content

Commit bd0fa5a

Browse files
committed
feat: performance optimization and iOS scroll issue fix
1 parent fa7b2ac commit bd0fa5a

File tree

12 files changed

+233
-230
lines changed

12 files changed

+233
-230
lines changed

projects/sample-app/src/app/app.component.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,7 @@
4545
<ng-template #emojiPickerTemplate let-emojiInput$="emojiInput$">
4646
<app-emoji-picker [emojiInput$]="emojiInput$"></app-emoji-picker>
4747
</ng-template>
48+
49+
<ng-template #avatar>
50+
<div>{{ counter }}</div>
51+
</ng-template>

projects/sample-app/src/app/app.component.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
EmojiPickerContext,
1414
CustomTemplatesService,
1515
ThemeService,
16+
AvatarContext,
1617
} from 'stream-chat-angular';
1718
import { environment } from '../environments/environment';
1819

@@ -26,8 +27,10 @@ export class AppComponent implements AfterViewInit {
2627
isThreadOpen = false;
2728
@ViewChild('emojiPickerTemplate')
2829
emojiPickerTemplate!: TemplateRef<EmojiPickerContext>;
30+
@ViewChild('avatar') avatarTemplate!: TemplateRef<AvatarContext>;
2931
themeVersion: '1' | '2';
3032
theme$: Observable<string>;
33+
counter = 0;
3134

3235
constructor(
3336
private chatService: ChatClientService,
@@ -44,19 +47,23 @@ export class AppComponent implements AfterViewInit {
4447
void this.channelService.init({
4548
type: 'messaging',
4649
members: { $in: [environment.userId] },
50+
// id: { $eq: '1af49475-b988-479e-9444-2a10aab707f0' },
4751
});
4852
this.streamI18nService.setTranslation();
4953
this.channelService.activeParentMessage$
5054
.pipe(map((m) => !!m))
5155
.subscribe((isThreadOpen) => (this.isThreadOpen = isThreadOpen));
5256
this.themeVersion = themeService.themeVersion;
5357
this.theme$ = themeService.theme$;
58+
59+
// setInterval(() => this.counter++, 1000);
5460
}
5561

5662
ngAfterViewInit(): void {
5763
this.customTemplateService.emojiPickerTemplate$.next(
5864
this.emojiPickerTemplate
5965
);
66+
// this.customTemplateService.avatarTemplate$.next(this.avatarTemplate);
6067
}
6168

6269
closeMenu() {

projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.html

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
<ng-container *ngIf="isImage(attachment)">
2020
<ng-container
2121
*ngTemplateOutlet="
22-
imageAttachmentTemplate || defaultImage;
22+
(customTemplatesService.imageAttachmentTemplate$ | async) ||
23+
defaultImage;
2324
context: getAttachmentContext(attachment)
2425
"
2526
></ng-container>
@@ -70,7 +71,8 @@
7071
<ng-container *ngIf="isGallery(attachment)">
7172
<ng-container
7273
*ngTemplateOutlet="
73-
galleryAttachmentTemplate || defaultGallery;
74+
(customTemplatesService.galleryAttachmentTemplate$ | async) ||
75+
defaultGallery;
7476
context: getAttachmentContext(attachment)
7577
"
7678
></ng-container>
@@ -202,7 +204,8 @@
202204
<ng-container *ngIf="isVideo(attachment)">
203205
<ng-container
204206
*ngTemplateOutlet="
205-
videoAttachmentTemplate || defaultVideo;
207+
(customTemplatesService.videoAttachmentTemplate$ | async) ||
208+
defaultVideo;
206209
context: getAttachmentContext(attachment)
207210
"
208211
></ng-container>
@@ -249,7 +252,8 @@
249252
<ng-container *ngIf="isFile(attachment)">
250253
<ng-container
251254
*ngTemplateOutlet="
252-
fileAttachmentTemplate || defaultFile;
255+
(customTemplatesService.fileAttachmentTemplate$ | async) ||
256+
defaultFile;
253257
context: getAttachmentContext(attachment)
254258
"
255259
></ng-container>
@@ -314,7 +318,8 @@
314318
>
315319
<ng-container
316320
*ngTemplateOutlet="
317-
cardAttachmentTemplate || defaultCard;
321+
(customTemplatesService.cardAttachmentTemplate$ | async) ||
322+
defaultCard;
318323
context: getAttachmentContext(attachment)
319324
"
320325
></ng-container>
@@ -384,7 +389,8 @@
384389
<ng-container *ngIf="attachment.actions && attachment.actions.length > 0">
385390
<ng-container
386391
*ngTemplateOutlet="
387-
attachmentActionsTemplate || defaultActions;
392+
(customTemplatesService.attachmentActionsTemplate$ | async) ||
393+
defaultActions;
388394
context: getAttachmentContext(attachment)
389395
"
390396
></ng-container>

projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import {
44
HostBinding,
55
Input,
66
OnChanges,
7-
OnDestroy,
8-
OnInit,
97
Output,
108
SimpleChanges,
119
TemplateRef,
@@ -26,7 +24,6 @@ import { ChannelService } from '../channel.service';
2624
import { CustomTemplatesService } from '../custom-templates.service';
2725
import { AttachmentConfigurationService } from '../attachment-configuration.service';
2826
import { ThemeService } from '../theme.service';
29-
import { Subscription } from 'rxjs';
3027

3128
/**
3229
* The `AttachmentList` component displays the attachments of a message
@@ -36,7 +33,7 @@ import { Subscription } from 'rxjs';
3633
templateUrl: './attachment-list.component.html',
3734
styles: [],
3835
})
39-
export class AttachmentListComponent implements OnChanges, OnInit, OnDestroy {
36+
export class AttachmentListComponent implements OnChanges {
4037
/**
4138
* The id of the message the attachments belong to
4239
*/
@@ -60,12 +57,6 @@ export class AttachmentListComponent implements OnChanges, OnInit, OnDestroy {
6057
imagesToView: Attachment<DefaultStreamChatGenerics>[] = [];
6158
imagesToViewCurrentIndex = 0;
6259
themeVersion: '1' | '2';
63-
imageAttachmentTemplate?: TemplateRef<AttachmentContext>;
64-
videoAttachmentTemplate?: TemplateRef<AttachmentContext>;
65-
galleryAttachmentTemplate?: TemplateRef<AttachmentContext>;
66-
fileAttachmentTemplate?: TemplateRef<AttachmentContext>;
67-
cardAttachmentTemplate?: TemplateRef<AttachmentContext>;
68-
attachmentActionsTemplate?: TemplateRef<AttachmentContext>;
6960
@ViewChild('modalContent', { static: true })
7061
private modalContent!: TemplateRef<void>;
7162
private attachmentConfigurations: Map<
@@ -74,7 +65,6 @@ export class AttachmentListComponent implements OnChanges, OnInit, OnDestroy {
7465
| VideoAttachmentConfiguration
7566
| ImageAttachmentConfiguration
7667
> = new Map();
77-
private subscriptions: Subscription[] = [];
7868

7969
constructor(
8070
public readonly customTemplatesService: CustomTemplatesService,
@@ -84,38 +74,6 @@ export class AttachmentListComponent implements OnChanges, OnInit, OnDestroy {
8474
) {
8575
this.themeVersion = themeService.themeVersion;
8676
}
87-
ngOnInit(): void {
88-
this.subscriptions.push(
89-
this.customTemplatesService.imageAttachmentTemplate$.subscribe(
90-
(t) => (this.imageAttachmentTemplate = t)
91-
)
92-
);
93-
this.subscriptions.push(
94-
this.customTemplatesService.galleryAttachmentTemplate$.subscribe(
95-
(t) => (this.galleryAttachmentTemplate = t)
96-
)
97-
);
98-
this.subscriptions.push(
99-
this.customTemplatesService.videoAttachmentTemplate$.subscribe(
100-
(t) => (this.videoAttachmentTemplate = t)
101-
)
102-
);
103-
this.subscriptions.push(
104-
this.customTemplatesService.fileAttachmentTemplate$.subscribe(
105-
(t) => (this.fileAttachmentTemplate = t)
106-
)
107-
);
108-
this.subscriptions.push(
109-
this.customTemplatesService.cardAttachmentTemplate$.subscribe(
110-
(t) => (this.cardAttachmentTemplate = t)
111-
)
112-
);
113-
this.subscriptions.push(
114-
this.customTemplatesService.attachmentActionsTemplate$.subscribe(
115-
(t) => (this.attachmentActionsTemplate = t)
116-
)
117-
);
118-
}
11977

12078
ngOnChanges(changes: SimpleChanges): void {
12179
if (changes.attachments) {
@@ -137,10 +95,6 @@ export class AttachmentListComponent implements OnChanges, OnInit, OnDestroy {
13795
}
13896
}
13997

140-
ngOnDestroy(): void {
141-
this.subscriptions.forEach((s) => s.unsubscribe());
142-
}
143-
14498
trackByUrl(_: number, attachment: Attachment) {
14599
return (
146100
attachment.image_url ||

projects/stream-chat-angular/src/lib/channel.service.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,13 +1169,16 @@ export class ChannelService<
11691169
channel.on('message.read', (e) => {
11701170
this.ngZone.run(() => {
11711171
let latestMessage!: StreamMessage;
1172-
this.activeChannelMessages$.pipe(first()).subscribe((messages) => {
1172+
let messages!: StreamMessage[];
1173+
this.activeChannelMessages$.pipe(first()).subscribe((m) => {
1174+
messages = m;
11731175
latestMessage = messages[messages.length - 1];
11741176
});
11751177
if (!latestMessage || !e.user) {
11761178
return;
11771179
}
11781180
latestMessage.readBy = getReadBy(latestMessage, channel);
1181+
messages[messages.length - 1] = { ...latestMessage };
11791182

11801183
this.activeChannelMessagesSubject.next(
11811184
this.activeChannelMessagesSubject.getValue()
@@ -1251,14 +1254,17 @@ export class ChannelService<
12511254
)
12521255
.pipe(first())
12531256
.subscribe((m) => (messages = m));
1254-
const message = messages.find((m) => m.id === e?.message?.id);
1255-
if (!message) {
1257+
const messageIndex = messages.findIndex((m) => m.id === e?.message?.id);
1258+
if (messageIndex === -1) {
12561259
return;
12571260
}
1261+
const message = messages[messageIndex];
12581262
message.reaction_counts = { ...e.message?.reaction_counts };
12591263
message.reaction_scores = { ...e.message?.reaction_scores };
12601264
message.latest_reactions = [...(e.message?.latest_reactions || [])];
12611265
message.own_reactions = [...(e.message?.own_reactions || [])];
1266+
1267+
messages[messageIndex] = { ...message };
12621268
isThreadMessage
12631269
? this.activeThreadMessagesSubject.next([...messages])
12641270
: this.activeChannelMessagesSubject.next([...messages]);

projects/stream-chat-angular/src/lib/chat-client.service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ export class ChatClientService<
118118
);
119119
throw error;
120120
}
121-
this.userSubject.next(this.chatClient.user);
121+
this.userSubject.next(
122+
this.chatClient.user ? { ...this.chatClient.user } : undefined
123+
);
122124
const sdkPrefix = 'stream-chat-angular';
123125
if (!this.chatClient.getUserAgent().includes(sdkPrefix)) {
124126
this.chatClient.setUserAgent(

projects/stream-chat-angular/src/lib/message-actions-box/message-actions-box.component.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
>
1111
<ng-container
1212
*ngTemplateOutlet="
13-
messageActionItemTemplate || defaultMessageActionItem;
13+
(customTemplatesService.messageActionsBoxItemTemplate$ | async) ||
14+
defaultMessageActionItem;
1415
context: getMessageActionTemplateContext(item)
1516
"
1617
></ng-container>
@@ -37,7 +38,7 @@
3738

3839
<ng-container
3940
*ngTemplateOutlet="
40-
modalTemplate || defaultModal;
41+
(customTemplatesService.modalTemplate$ | async) || defaultModal;
4142
context: getEditModalContext()
4243
"
4344
></ng-container>
@@ -72,7 +73,7 @@
7273
</ng-template>
7374
<ng-container
7475
*ngTemplateOutlet="
75-
messageInputTemplate || defaultInput;
76+
(customTemplatesService.messageInputTemplate$ | async) || defaultInput;
7677
context: getMessageInputContext()
7778
"
7879
>

projects/stream-chat-angular/src/lib/message-actions-box/message-actions-box.component.ts

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ import {
33
EventEmitter,
44
Input,
55
OnChanges,
6-
OnDestroy,
76
Output,
87
SimpleChanges,
98
TemplateRef,
109
ViewChild,
1110
} from '@angular/core';
12-
import { Observable, Subject, Subscription } from 'rxjs';
11+
import { Observable, Subject } from 'rxjs';
1312
import { ChannelService } from '../channel.service';
1413
import { ChatClientService } from '../chat-client.service';
1514
import { CustomTemplatesService } from '../custom-templates.service';
@@ -30,7 +29,7 @@ import {
3029
templateUrl: './message-actions-box.component.html',
3130
styles: [],
3231
})
33-
export class MessageActionsBoxComponent implements OnChanges, OnDestroy {
32+
export class MessageActionsBoxComponent implements OnChanges {
3433
/**
3534
* Indicates if the list should be opened or closed. Adding a UI element to open and close the list is the parent's component responsibility.
3635
* @deprecated No need for this since [theme-v2](../theming/introduction.mdx)
@@ -66,7 +65,6 @@ export class MessageActionsBoxComponent implements OnChanges, OnDestroy {
6665
| TemplateRef<MessageActionBoxItemContext>
6766
| undefined;
6867
modalTemplate: TemplateRef<ModalContext> | undefined;
69-
subscriptions: Subscription[] = [];
7068
visibleMessageActionItems: (MessageActionItem | CustomMessageActionItem)[] =
7169
[];
7270
sendMessage$: Observable<void>;
@@ -78,23 +76,8 @@ export class MessageActionsBoxComponent implements OnChanges, OnDestroy {
7876
private chatClientService: ChatClientService,
7977
private notificationService: NotificationService,
8078
private channelService: ChannelService,
81-
private customTemplatesService: CustomTemplatesService
79+
public readonly customTemplatesService: CustomTemplatesService
8280
) {
83-
this.subscriptions.push(
84-
this.customTemplatesService.messageInputTemplate$.subscribe(
85-
(template) => (this.messageInputTemplate = template)
86-
)
87-
);
88-
this.subscriptions.push(
89-
this.customTemplatesService.messageActionsBoxItemTemplate$.subscribe(
90-
(template) => (this.messageActionItemTemplate = template)
91-
)
92-
);
93-
this.subscriptions.push(
94-
this.customTemplatesService.modalTemplate$.subscribe(
95-
(template) => (this.modalTemplate = template)
96-
)
97-
);
9881
this.messageActionItems = [
9982
{
10083
actionName: 'quote',
@@ -185,10 +168,6 @@ export class MessageActionsBoxComponent implements OnChanges, OnDestroy {
185168
}
186169
}
187170

188-
ngOnDestroy(): void {
189-
this.subscriptions.forEach((s) => s.unsubscribe());
190-
}
191-
192171
getActionLabel(
193172
actionLabelOrTranslationKey: ((message: StreamMessage) => string) | string
194173
) {

projects/stream-chat-angular/src/lib/message-list/message-list.component.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ export class MessageListComponent
5454
*/
5555
@Input() messageOptionsTrigger: 'message-row' | 'message-bubble' =
5656
'message-row';
57-
typingIndicatorTemplate: TemplateRef<TypingIndicatorContext> | undefined;
5857
/**
5958
* You can hide the "jump to latest" button while scrolling. A potential use-case for this input would be to [workaround a known issue on iOS Safar](https://github.com/GetStream/stream-chat-angular/issues/418)
6059
*/
@@ -81,6 +80,7 @@ export class MessageListComponent
8180
* You can turn on and off the loading indicator that signals to users that more messages are being loaded to the message list
8281
*/
8382
@Input() displayLoadingIndicator = true;
83+
typingIndicatorTemplate: TemplateRef<TypingIndicatorContext> | undefined;
8484
messageTemplate: TemplateRef<MessageContext> | undefined;
8585
customDateSeparatorTemplate: TemplateRef<DateSeparatorContext> | undefined;
8686
customnewMessagesIndicatorTemplate: TemplateRef<void> | undefined;
@@ -358,6 +358,7 @@ export class MessageListComponent
358358
scrollToBottom(): void {
359359
this.scrollContainer.nativeElement.scrollTop =
360360
this.scrollContainer.nativeElement.scrollHeight;
361+
this.forceRepaint();
361362
}
362363

363364
scrollToTop() {
@@ -460,6 +461,14 @@ export class MessageListComponent
460461
this.scrollContainer.nativeElement.scrollTop =
461462
(this.prevScrollTop || 0) +
462463
(this.scrollContainer.nativeElement.scrollHeight - this.containerHeight!);
464+
this.forceRepaint();
465+
}
466+
467+
private forceRepaint() {
468+
// Solves the issue of empty screen on iOS Safari when scrolling
469+
this.scrollContainer.nativeElement.style.display = 'none';
470+
this.scrollContainer.nativeElement.offsetHeight; // no need to store this anywhere, the reference is enough
471+
this.scrollContainer.nativeElement.style.display = '';
463472
}
464473

465474
private getScrollPosition(): 'top' | 'bottom' | 'middle' {

0 commit comments

Comments
 (0)