Skip to content

Commit a5e738e

Browse files
authored
Merge pull request #263 from GetStream/member-list-if-no-channel-name
Member list if no channel name
2 parents d14bdc9 + 1bfb257 commit a5e738e

18 files changed

+337
-110
lines changed

docusaurus/docs/Angular/components/MessageComponent.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ The `Message` component uses the [`parseDate`](https://github.com/GetStream/stre
4646

4747
### Read by
4848

49-
The `Message` component uses the [`getReadByText`](https://github.com/GetStream/stream-chat-angular/tree/master/projects/stream-chat-angular/src/lib/message/read-by-text.ts) utility function to display the list of people who have read a particular message in a user friendly way (for example _Bob, Sophie, Jack, Rose, John and 1 more_).
49+
The `Message` component uses the [`listUsers`](https://github.com/GetStream/stream-chat-angular/tree/master/projects/stream-chat-angular/src/lib/list-users.ts) utility function to display the list of people who have read a particular message in a user friendly way (for example _Bob, Sophie, Jack, Rose, John and 1 more_).
5050

5151
### Loading indicator
5252

docusaurus/docs/Angular/components/MessageListComponent.mdx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ The `MessageList` displays messages using pagination, new messages are loaded wh
77

88
## Customization
99

10-
**Example 1** - Using a custom message list component
11-
12-
## Customization
13-
1410
See [our customization guide](../concepts/customization.mdx) on how to provide your own message list component.
1511

1612
:::note

projects/stream-chat-angular/src/lib/channel-header/channel-header.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
</div>
99
<stream-avatar-placeholder
1010
imageUrl="{{ activeChannel?.data?.image }}"
11-
name="{{ activeChannel?.data?.name }}"
11+
name="{{ avatarName }}"
1212
></stream-avatar-placeholder>
1313
<div class="str-chat__header-livestream-left">
1414
<p data-testid="name" class="str-chat__header-livestream-left--title">
15-
{{ activeChannel?.data?.name }}
15+
{{ displayText }}
1616
</p>
1717
<p data-testid="info" class="str-chat__header-livestream-left--members">
1818
{{'streamChat.{{ memberCount }} members' | translate:memberCountParam}}

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
22
import { TranslateModule } from '@ngx-translate/core';
33
import { Subject } from 'rxjs';
4-
import { Channel } from 'stream-chat';
4+
import { Channel, UserResponse } from 'stream-chat';
55
import { ChannelService } from '../channel.service';
6+
import { ChatClientService } from '../chat-client.service';
67
import { StreamI18nService } from '../stream-i18n.service';
78
import { ChannelHeaderComponent } from './channel-header.component';
89

@@ -14,15 +15,24 @@ describe('ChannelHeaderComponent', () => {
1415
let channelServiceMock: {
1516
activeChannel$: Subject<Channel>;
1617
};
18+
let chatClientServiceMock: {
19+
chatClient: { user: UserResponse };
20+
};
1721

1822
beforeEach(() => {
1923
channelServiceMock = {
2024
activeChannel$: new Subject(),
2125
};
26+
chatClientServiceMock = {
27+
chatClient: { user: { id: 'currentUser' } },
28+
};
2229
TestBed.configureTestingModule({
2330
imports: [TranslateModule.forRoot()],
2431
declarations: [ChannelHeaderComponent],
25-
providers: [{ provide: ChannelService, useValue: channelServiceMock }],
32+
providers: [
33+
{ provide: ChannelService, useValue: channelServiceMock },
34+
{ provide: ChatClientService, useValue: chatClientServiceMock },
35+
],
2636
});
2737
TestBed.inject(StreamI18nService).setTranslation();
2838
fixture = TestBed.createComponent(ChannelHeaderComponent);
@@ -35,19 +45,27 @@ describe('ChannelHeaderComponent', () => {
3545
it('should display members count', () => {
3646
channelServiceMock.activeChannel$.next({
3747
data: { member_count: 6 },
48+
state: {},
3849
} as any as Channel);
3950
fixture.detectChanges();
4051

4152
expect(queryInfo()?.textContent).toContain('6 members');
4253
});
4354

44-
it('should display channel name', () => {
55+
it('should display channel display text', () => {
4556
channelServiceMock.activeChannel$.next({
46-
data: { name: 'Book club' },
57+
state: {
58+
members: {
59+
user1: { user: { id: 'user1', name: 'Ben' } },
60+
[chatClientServiceMock.chatClient.user.id]: {
61+
user: { id: chatClientServiceMock.chatClient.user.id },
62+
},
63+
},
64+
},
4765
} as any as Channel);
4866
fixture.detectChanges();
4967

50-
expect(queryName()?.textContent).toContain('Book club');
68+
expect(queryName()?.textContent).toContain('Ben');
5169
});
5270

5371
it('should display watcher count', () => {

projects/stream-chat-angular/src/lib/channel-header/channel-header.component.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import { Subscription } from 'rxjs';
99
import { Channel } from 'stream-chat';
1010
import { ChannelListToggleService } from '../channel-list/channel-list-toggle.service';
1111
import { ChannelService } from '../channel.service';
12+
import { ChatClientService } from '../chat-client.service';
1213
import { CustomTemplatesService } from '../custom-templates.service';
14+
import { getChannelDisplayText } from '../get-channel-display-text';
1315
import { ChannelActionsContext, DefaultStreamChatGenerics } from '../types';
1416

1517
/**
@@ -30,7 +32,8 @@ export class ChannelHeaderComponent implements OnInit, OnDestroy {
3032
private channelService: ChannelService,
3133
private channelListToggleService: ChannelListToggleService,
3234
private customTemplatesService: CustomTemplatesService,
33-
private cdRef: ChangeDetectorRef
35+
private cdRef: ChangeDetectorRef,
36+
private chatClientService: ChatClientService
3437
) {
3538
this.channelService.activeChannel$.subscribe((c) => {
3639
this.activeChannel = c;
@@ -74,4 +77,27 @@ export class ChannelHeaderComponent implements OnInit, OnDestroy {
7477
get watcherCountParam() {
7578
return { watcherCount: this.activeChannel?.state?.watcher_count || 0 };
7679
}
80+
81+
get displayText() {
82+
if (!this.activeChannel) {
83+
return '';
84+
}
85+
return getChannelDisplayText(
86+
this.activeChannel,
87+
this.chatClientService.chatClient.user!
88+
);
89+
}
90+
91+
get avatarName() {
92+
if (this.activeChannel?.data?.name) {
93+
return this.activeChannel?.data?.name;
94+
}
95+
const otherMembers = Object.values(
96+
this.activeChannel?.state.members || {}
97+
).filter((m) => m.user_id !== this.chatClientService.chatClient.user!.id);
98+
if (otherMembers.length === 1) {
99+
return otherMembers[0].user?.name || otherMembers[0].user?.name;
100+
}
101+
return '#';
102+
}
77103
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { By } from '@angular/platform-browser';
33
import { TranslateModule } from '@ngx-translate/core';
44
import { ChannelPreviewComponent } from '../channel-preview/channel-preview.component';
55
import { ChannelService } from '../channel.service';
6+
import { ChatClientService } from '../chat-client.service';
67
import {
78
generateMockChannels,
89
mockChannelService,
@@ -28,7 +29,13 @@ describe('ChannelListComponent', () => {
2829
TestBed.configureTestingModule({
2930
imports: [TranslateModule.forRoot()],
3031
declarations: [ChannelListComponent, ChannelPreviewComponent],
31-
providers: [{ provide: ChannelService, useValue: channelServiceMock }],
32+
providers: [
33+
{ provide: ChannelService, useValue: channelServiceMock },
34+
{
35+
provide: ChatClientService,
36+
useValue: { chatClient: { user: { id: 'userid' } } },
37+
},
38+
],
3239
});
3340
fixture = TestBed.createComponent(ChannelListComponent);
3441
nativeElement = fixture.nativeElement as HTMLElement;

projects/stream-chat-angular/src/lib/channel-preview/channel-preview.component.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
></stream-avatar-placeholder>
1414
</div>
1515
<div class="str-chat__channel-preview-messenger--right">
16-
<div class="str-chat__channel-preview-messenger--name">
16+
<div
17+
style="position: relative"
18+
class="str-chat__channel-preview-messenger--name"
19+
>
1720
<span data-testid="channel-preview-title">{{ title }}</span>
1821
</div>
1922
<div

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

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
22
import { By } from '@angular/platform-browser';
33
import { TranslateModule } from '@ngx-translate/core';
4+
import { UserResponse } from 'stream-chat';
45
import { AvatarPlaceholderComponent } from '../avatar-placeholder/avatar-placeholder.component';
56
import { AvatarComponent } from '../avatar/avatar.component';
67
import { ChannelService } from '../channel.service';
8+
import { ChatClientService } from '../chat-client.service';
79
import {
810
generateMockChannels,
911
mockChannelService,
@@ -17,21 +19,30 @@ describe('ChannelPreviewComponent', () => {
1719
let component: ChannelPreviewComponent;
1820
let nativeElement: HTMLElement;
1921
let channelServiceMock: MockChannelService;
22+
let chatClientServiceMock: {
23+
chatClient: { user: UserResponse };
24+
};
2025
let queryContainer: () => HTMLElement | null;
2126
let queryAvatar: () => AvatarPlaceholderComponent;
2227
let queryTitle: () => HTMLElement | null;
2328
let queryLatestMessage: () => HTMLElement | null;
2429

2530
beforeEach(() => {
2631
channelServiceMock = mockChannelService();
32+
chatClientServiceMock = {
33+
chatClient: { user: { id: 'currentUser' } },
34+
};
2735
TestBed.configureTestingModule({
2836
imports: [TranslateModule.forRoot()],
2937
declarations: [
3038
ChannelPreviewComponent,
3139
AvatarComponent,
3240
AvatarPlaceholderComponent,
3341
],
34-
providers: [{ provide: ChannelService, useValue: channelServiceMock }],
42+
providers: [
43+
{ provide: ChannelService, useValue: channelServiceMock },
44+
{ provide: ChatClientService, useValue: chatClientServiceMock },
45+
],
3546
});
3647
fixture = TestBed.createComponent(ChannelPreviewComponent);
3748
component = fixture.componentInstance;
@@ -146,13 +157,19 @@ describe('ChannelPreviewComponent', () => {
146157
expect(avatar.imageUrl).toBe(channel.data?.image as string);
147158
});
148159

149-
it('should display channel name', () => {
160+
it('should display channel display text', () => {
150161
const channel = generateMockChannels()[0];
151-
channelServiceMock.activeChannel$.next(channel);
162+
channel.data!.name = undefined;
163+
channel.state.members = {
164+
user1: { user: { id: 'user1', name: 'Ben' } },
165+
[chatClientServiceMock.chatClient.user.id]: {
166+
user: { id: chatClientServiceMock.chatClient.user.id },
167+
},
168+
};
152169
component.channel = channel;
153170
fixture.detectChanges();
154171

155-
expect(queryTitle()?.textContent).toBe(channel.data?.name);
172+
expect(queryTitle()?.textContent).toContain('Ben');
156173
});
157174

158175
describe('should display latest message of channel', () => {
@@ -263,4 +280,47 @@ describe('ChannelPreviewComponent', () => {
263280

264281
expect(queryLatestMessage()?.textContent).toContain('Nothing yet...');
265282
});
283+
284+
it('should use "#" as avatar name fallback', () => {
285+
const channel = generateMockChannels()[0];
286+
channel.data!.name = undefined;
287+
component.channel = channel;
288+
fixture.detectChanges();
289+
290+
expect(component.avatarName).toBe('#');
291+
});
292+
293+
it('should use the name of the other member if channel only has two members', () => {
294+
const channel = generateMockChannels()[0];
295+
channel.data!.name = undefined;
296+
channel.state.members = {
297+
otheruser: {
298+
user_id: 'otheruser',
299+
user: { id: 'otheruser', name: 'Jack' },
300+
},
301+
[chatClientServiceMock.chatClient.user.id]: {
302+
user_id: chatClientServiceMock.chatClient.user.id,
303+
user: { id: chatClientServiceMock.chatClient.user.id, name: 'Sara' },
304+
},
305+
};
306+
component.channel = channel;
307+
fixture.detectChanges();
308+
309+
expect(component.avatarName).toBe('Jack');
310+
311+
channel.state.members = {
312+
otheruser: {
313+
user_id: 'otheruser',
314+
user: { id: 'otheruser', name: 'Jack' },
315+
},
316+
otheruser2: {
317+
user_id: 'otheruser2',
318+
user: { id: 'otheruser2', name: 'Sara' },
319+
},
320+
};
321+
component.channel = channel;
322+
fixture.detectChanges();
323+
324+
expect(component.avatarName).toBe('#');
325+
});
266326
});

projects/stream-chat-angular/src/lib/channel-preview/channel-preview.component.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import {
77
MessageResponse,
88
} from 'stream-chat';
99
import { ChannelService } from '../channel.service';
10+
import { getChannelDisplayText } from '../get-channel-display-text';
1011
import { DefaultStreamChatGenerics } from '../types';
12+
import { ChatClientService } from '../chat-client.service';
1113

1214
/**
1315
* The `ChannelPreview` component displays a channel preview in the channel list, it consists of the image, name and latest message of the channel.
@@ -28,7 +30,11 @@ export class ChannelPreviewComponent implements OnInit, OnDestroy {
2830
private subscriptions: (Subscription | { unsubscribe: () => void })[] = [];
2931
private canSendReadEvents = true;
3032

31-
constructor(private channelService: ChannelService, private ngZone: NgZone) {}
33+
constructor(
34+
private channelService: ChannelService,
35+
private ngZone: NgZone,
36+
private chatClientService: ChatClientService
37+
) {}
3238

3339
ngOnInit(): void {
3440
this.subscriptions.push(
@@ -76,11 +82,26 @@ export class ChannelPreviewComponent implements OnInit, OnDestroy {
7682
}
7783

7884
get avatarName() {
79-
return this.channel?.data?.name;
85+
if (this.channel?.data?.name) {
86+
return this.channel?.data?.name;
87+
}
88+
const otherMembers = Object.values(
89+
this.channel?.state.members || {}
90+
).filter((m) => m.user_id !== this.chatClientService.chatClient.user!.id);
91+
if (otherMembers.length === 1) {
92+
return otherMembers[0].user?.name || otherMembers[0].user?.name;
93+
}
94+
return '#';
8095
}
8196

8297
get title() {
83-
return this.channel?.data?.name;
98+
if (!this.channel) {
99+
return '';
100+
}
101+
return getChannelDisplayText(
102+
this.channel,
103+
this.chatClientService.chatClient.user!
104+
);
84105
}
85106

86107
setAsActiveChannel(): void {

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export class ChannelService<
7676
* :::important
7777
* If you want to subscribe to channel events, you need to manually reenter Angular's change detection zone, our [Change detection guide](../concepts/change-detection.mdx) explains this in detail.
7878
* :::
79+
*
80+
* The active channel will always be marked as read when a new message is received
7981
*/
8082
activeChannel$: Observable<Channel<T> | undefined>;
8183
/**
@@ -344,7 +346,7 @@ export class ChannelService<
344346
}
345347

346348
/**
347-
* Sets the given `channel` as active.
349+
* Sets the given `channel` as active and marks it as read.
348350
* @param channel
349351
*/
350352
setAsActiveChannel(channel: Channel<T>) {
@@ -443,7 +445,7 @@ export class ChannelService<
443445
}
444446

445447
/**
446-
* Queries the channels with the given filters, sorts and options. More info about [channel querying](https://getstream.io/chat/docs/javascript/query_channels/?language=javascript) can be found in the platform documentation.
448+
* Queries the channels with the given filters, sorts and options. More info about [channel querying](https://getstream.io/chat/docs/javascript/query_channels/?language=javascript) can be found in the platform documentation. By default the first channel in the list will be set as active channel and will be marked as read.
447449
* @param filters
448450
* @param sort
449451
* @param options

0 commit comments

Comments
 (0)