Skip to content

Commit 36ccfbb

Browse files
authored
Merge pull request #111 from GetStream/reaction-toggle-fix
fix: Message reactions toggle #108
2 parents 006c2ee + 6053033 commit 36ccfbb

File tree

6 files changed

+91
-25
lines changed

6 files changed

+91
-25
lines changed

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

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
ChatClientService,
44
ChannelService,
55
StreamI18nService,
6-
ThemeService,
76
} from 'stream-chat-angular';
87
import { environment } from '../environments/environment';
98

@@ -16,8 +15,7 @@ export class AppComponent {
1615
constructor(
1716
private chatService: ChatClientService,
1817
private channelService: ChannelService,
19-
private streamI18nService: StreamI18nService,
20-
private themeService: ThemeService
18+
private streamI18nService: StreamI18nService
2119
) {
2220
void this.chatService.init(
2321
environment.apiKey,
@@ -29,12 +27,5 @@ export class AppComponent {
2927
members: { $in: [environment.userId] },
3028
});
3129
this.streamI18nService.setTranslation();
32-
this.themeService.customLightThemeVariables = {
33-
'--primary-color': 'lightgreen',
34-
};
35-
this.themeService.customDarkThemeVariables = {
36-
'--primary-color': 'darkgreen',
37-
};
38-
this.themeService.theme$.next('dark');
3930
}
4031
}

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

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -361,9 +361,12 @@ export class ChannelService {
361361
private watchForActiveChannelEvents(channel: Channel) {
362362
this.activeChannelSubscriptions.push(
363363
channel.on('message.new', () => {
364-
this.activeChannelMessagesSubject.next([...channel.state.messages]);
365-
this.activeChannel$.pipe(first()).subscribe((c) => void c?.markRead());
366-
this.appRef.tick();
364+
this.ngZone.run(() => {
365+
this.activeChannelMessagesSubject.next([...channel.state.messages]);
366+
this.activeChannel$
367+
.pipe(first())
368+
.subscribe((c) => void c?.markRead());
369+
});
367370
})
368371
);
369372
this.activeChannelSubscriptions.push(
@@ -398,22 +401,17 @@ export class ChannelService {
398401
}
399402

400403
private messageReactionEventReceived(e: Event) {
401-
let message: StreamMessage | undefined;
402-
this.activeChannelMessages$
403-
.pipe(first())
404-
.subscribe(
405-
(messages) => (message = messages.find((m) => m.id === e.message?.id))
406-
);
404+
let messages!: StreamMessage[];
405+
this.activeChannelMessages$.pipe(first()).subscribe((m) => (messages = m));
406+
const message = messages.find((m) => m.id === e?.message?.id);
407407
if (!message) {
408408
return;
409409
}
410410
message.reaction_counts = { ...e.message?.reaction_counts };
411411
message.reaction_scores = { ...e.message?.reaction_scores };
412412
message.latest_reactions = [...(e.message?.latest_reactions || [])];
413413
message.own_reactions = [...(e.message?.own_reactions || [])];
414-
this.activeChannelMessagesSubject.next(
415-
this.activeChannelMessagesSubject.getValue()
416-
);
414+
this.activeChannelMessagesSubject.next([...messages]);
417415
this.appRef.tick();
418416
}
419417

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

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { ComponentFixture, TestBed } from '@angular/core/testing';
1+
import {
2+
ComponentFixture,
3+
fakeAsync,
4+
TestBed,
5+
tick,
6+
} from '@angular/core/testing';
27
import { ReactionResponse } from 'stream-chat';
38
import { By } from '@angular/platform-browser';
49
import { AvatarComponent } from '../avatar/avatar.component';
@@ -9,6 +14,7 @@ import {
914
} from './message-reactions.component';
1015
import { EmojiModule } from '@ctrl/ngx-emoji-mart/ngx-emoji';
1116
import { ChannelService } from '../channel.service';
17+
import { SimpleChange } from '@angular/core';
1218

1319
describe('MessageReactionsComponent', () => {
1420
let component: MessageReactionsComponent;
@@ -216,4 +222,40 @@ describe('MessageReactionsComponent', () => {
216222
'like'
217223
);
218224
});
225+
226+
it('should emit #isSelectorOpenChange, if outside click happens', fakeAsync(() => {
227+
let eventHandler: Function | undefined;
228+
spyOn(window, 'addEventListener').and.callFake(
229+
(_: string, handler: any) => {
230+
eventHandler = handler as Function;
231+
}
232+
);
233+
const spy = jasmine.createSpy();
234+
component.isSelectorOpenChange.subscribe(spy);
235+
component.isSelectorOpen = true;
236+
component.ngOnChanges({
237+
isSelectorOpen: {} as any as SimpleChange,
238+
});
239+
tick();
240+
fixture.detectChanges();
241+
eventHandler!(queryReactionsSelector());
242+
243+
expect(spy).toHaveBeenCalledWith(false);
244+
}));
245+
246+
it('should only watch for outside clicks if selector is open', () => {
247+
const addEventListenerSpy = spyOn(window, 'addEventListener');
248+
const removeEventListenerSpy = spyOn(window, 'removeEventListener');
249+
component.isSelectorOpen = false;
250+
component.ngOnChanges({
251+
isSelectorOpen: {} as any as SimpleChange,
252+
});
253+
fixture.detectChanges();
254+
255+
expect(addEventListenerSpy).not.toHaveBeenCalled();
256+
expect(removeEventListenerSpy).toHaveBeenCalledWith(
257+
'click',
258+
jasmine.any(Function)
259+
);
260+
});
219261
});

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import {
33
ChangeDetectorRef,
44
Component,
55
ElementRef,
6+
EventEmitter,
67
Input,
8+
OnChanges,
9+
Output,
10+
SimpleChanges,
711
ViewChild,
812
} from '@angular/core';
913
import { ReactionResponse } from 'stream-chat';
@@ -31,11 +35,12 @@ const emojiReactionsMapping: { [key in MessageReactionType]: string } = {
3135
templateUrl: './message-reactions.component.html',
3236
styles: [],
3337
})
34-
export class MessageReactionsComponent implements AfterViewChecked {
38+
export class MessageReactionsComponent implements AfterViewChecked, OnChanges {
3539
@Input() messageId: string | undefined;
3640
@Input() messageReactionCounts: { [key in MessageReactionType]?: number } =
3741
{};
3842
@Input() isSelectorOpen: boolean = false;
43+
@Output() readonly isSelectorOpenChange = new EventEmitter<boolean>();
3944
@Input() latestReactions: ReactionResponse<
4045
DefaultReactionType,
4146
DefaultUserType
@@ -59,6 +64,14 @@ export class MessageReactionsComponent implements AfterViewChecked {
5964
private channelService: ChannelService
6065
) {}
6166

67+
ngOnChanges(changes: SimpleChanges): void {
68+
if (changes.isSelectorOpen) {
69+
this.isSelectorOpen
70+
? setTimeout(() => this.watchForOutsideClicks()) // setTimeout: wait for current click to bubble up, and only watch for clicks after that
71+
: this.stopWatchForOutsideClicks();
72+
}
73+
}
74+
6275
ngAfterViewChecked(): void {
6376
if (this.tooltipText && !this.tooltipPositions) {
6477
this.setTooltipPosition();
@@ -121,6 +134,20 @@ export class MessageReactionsComponent implements AfterViewChecked {
121134
: void this.channelService.addReaction(this.messageId!, type);
122135
}
123136

137+
private eventHandler = (event: Event) => {
138+
if (!this.selectorContainer?.nativeElement.contains(event.target as Node)) {
139+
this.isSelectorOpenChange.emit(false);
140+
}
141+
};
142+
143+
private watchForOutsideClicks() {
144+
window.addEventListener('click', this.eventHandler);
145+
}
146+
147+
private stopWatchForOutsideClicks() {
148+
window.removeEventListener('click', this.eventHandler);
149+
}
150+
124151
private setTooltipPosition() {
125152
const tooltip = this.selectorTooltip?.nativeElement.getBoundingClientRect();
126153
const target = this.currentTooltipTarget?.getBoundingClientRect();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
*ngIf="areReactionsEnabled"
8282
[messageReactionCounts]="message?.reaction_counts || {}"
8383
[latestReactions]="message?.latest_reactions || []"
84-
[isSelectorOpen]="isReactionSelectorOpen"
84+
[(isSelectorOpen)]="isReactionSelectorOpen"
8585
[messageId]="message?.id"
8686
[ownReactions]="message?.own_reactions || []"
8787
></stream-message-reactions>

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,14 @@ describe('MessageComponent', () => {
567567
fixture.detectChanges();
568568

569569
expect(queryMessageReactionsComponent()).toBeUndefined();
570+
571+
component.isReactionSelectorOpen = true;
572+
component.areReactionsEnabled = true;
573+
fixture.detectChanges();
574+
queryMessageReactionsComponent().isSelectorOpenChange.emit(false);
575+
fixture.detectChanges();
576+
577+
expect(component.isReactionSelectorOpen).toBeFalse();
570578
});
571579

572580
it('should toggle reactions selector', () => {

0 commit comments

Comments
 (0)