Skip to content

Commit 215c8e1

Browse files
authored
Merge pull request #641 from GetStream/channel-list-infinite-scroll
Channel list infinite scroll
2 parents c5224e9 + 4d4dbab commit 215c8e1

5 files changed

Lines changed: 57 additions & 68 deletions

File tree

docusaurus/docs/Angular/code-examples/responsive-layout.mdx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -204,20 +204,14 @@ Lastly, implement auto close behavior for the channel list:
204204

205205
```html
206206
<stream-channel-list
207-
(click)="closeMenu()"
207+
(click)="closeMenu($event)"
208208
class="channel-list menu-{{ isMenuOpen ? 'open' : 'close' }}"
209209
></stream-channel-list>
210210
```
211211

212212
```ts
213-
closeMenu() {
214-
let isChannelQueryInProgress = false;
215-
this.channelService.channelQueryState$.pipe(take(1)).subscribe((state) => {
216-
if (state?.state === 'in-progress') {
217-
isChannelQueryInProgress = true;
218-
}
219-
});
220-
if (!isChannelQueryInProgress) {
213+
closeMenu(event: Event) {
214+
if ((event.target as HTMLElement).closest('stream-channel-preview')) {
221215
this.isMenuOpen = false;
222216
}
223217
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
class="channel-list menu-{{ isMenuOpen ? 'open' : 'close' }} thread-{{
44
isThreadOpen ? 'open' : 'close'
55
}}"
6-
(click)="closeMenu()"
6+
(click)="closeMenu($event)"
77
></stream-channel-list>
8-
<stream-channel class="channel" (click)="closeMenu()">
8+
<stream-channel class="channel" (click)="closeMenu($event)">
99
<stream-channel-header>
1010
<button
1111
class="menu-button"

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

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
ViewChild,
66
} from '@angular/core';
77
import { Observable } from 'rxjs';
8-
import { map, take } from 'rxjs/operators';
8+
import { map } from 'rxjs/operators';
99
import {
1010
ChatClientService,
1111
ChannelService,
@@ -77,14 +77,8 @@ export class AppComponent implements AfterViewInit {
7777
);
7878
}
7979

80-
closeMenu() {
81-
let isChannelQueryInProgress = false;
82-
this.channelService.channelQueryState$.pipe(take(1)).subscribe((state) => {
83-
if (state?.state === 'in-progress') {
84-
isChannelQueryInProgress = true;
85-
}
86-
});
87-
if (!isChannelQueryInProgress) {
80+
closeMenu(event: Event) {
81+
if ((event.target as HTMLElement).closest('stream-channel-preview')) {
8882
this.isMenuOpen = false;
8983
}
9084
}

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

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -30,44 +30,29 @@
3030
>
3131
{{ "streamChat.You have no channels currently" | translate }}
3232
</p>
33-
<ng-container
34-
*ngFor="let channel of channels$ | async; trackBy: trackByChannelId"
33+
<stream-paginated-list
34+
[items]="(channels$ | async) ?? []"
35+
[hasMore]="(hasMoreChannels$ | async) ?? false"
36+
[isLoading]="isLoadingMoreChannels"
37+
(loadMore)="loadMoreChannels()"
3538
>
36-
<ng-template #defaultTemplate let-channelInput="channel">
37-
<stream-channel-preview
38-
data-testclass="channel-preview"
39-
[channel]="channelInput"
40-
></stream-channel-preview>
39+
<ng-template let-channel="item">
40+
<ng-template #defaultTemplate let-channelInput="channel">
41+
<stream-channel-preview
42+
data-testclass="channel-preview"
43+
[channel]="channelInput"
44+
></stream-channel-preview>
45+
</ng-template>
46+
<div>
47+
<ng-container
48+
*ngTemplateOutlet="
49+
customChannelPreviewTemplate || defaultTemplate;
50+
context: { channel: channel }
51+
"
52+
></ng-container>
53+
</div>
4154
</ng-template>
42-
<div>
43-
<ng-container
44-
*ngTemplateOutlet="
45-
customChannelPreviewTemplate || defaultTemplate;
46-
context: { channel: channel }
47-
"
48-
></ng-container>
49-
</div>
50-
</ng-container>
51-
<div
52-
*ngIf="hasMoreChannels$ | async"
53-
class="str-chat__load-more-button"
54-
data-testid="load-more"
55-
(click)="loadMoreChannels()"
56-
(keyup.enter)="loadMoreChannels()"
57-
>
58-
<button
59-
class="str-chat__load-more-button__button str-chat__cta-button"
60-
data-testid="load-more-button"
61-
[disabled]="isLoadingMoreChannels"
62-
>
63-
<span *ngIf="!isLoadingMoreChannels; else loadingIndicator">{{
64-
"Load more" | translate
65-
}}</span>
66-
<ng-template #loadingIndicator
67-
><stream-loading-indicator-placeholder></stream-loading-indicator-placeholder
68-
></ng-template>
69-
</button>
70-
</div>
55+
</stream-paginated-list>
7156
<ng-content select="[channel-list-bottom]"></ng-content>
7257
</div>
7358
</div>
@@ -89,7 +74,10 @@
8974
</ng-template>
9075

9176
<ng-template #loadingChannels>
92-
<div data-testid="loading-indicator" class="str-chat__loading-channels">
77+
<div
78+
data-testid="loading-indicator-full-size"
79+
class="str-chat__loading-channels"
80+
>
9381
<ng-container *ngTemplateOutlet="loadingChannel"></ng-container>
9482
<ng-container *ngTemplateOutlet="loadingChannel"></ng-container>
9583
<ng-container *ngTemplateOutlet="loadingChannel"></ng-container>

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

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
import { ThemeService } from '../theme.service';
1313
import { ChannelListComponent } from './channel-list.component';
1414
import { Subject, of } from 'rxjs';
15+
import { PaginatedListComponent } from '../paginated-list/paginated-list.component';
16+
import { Channel } from 'stream-chat';
1517

1618
describe('ChannelListComponent', () => {
1719
let channelServiceMock: MockChannelService;
@@ -21,14 +23,17 @@ describe('ChannelListComponent', () => {
2123
let queryChannels: () => ChannelPreviewComponent[];
2224
let queryChatdownContainer: () => HTMLElement | null;
2325
let queryLoadingIndicator: () => HTMLElement | null;
24-
let queryLoadMoreButton: () => HTMLElement | null;
2526
let queryEmptyIndicator: () => HTMLElement | null;
2627

2728
beforeEach(() => {
2829
channelServiceMock = mockChannelService();
2930
TestBed.configureTestingModule({
3031
imports: [TranslateModule.forRoot()],
31-
declarations: [ChannelListComponent, ChannelPreviewComponent],
32+
declarations: [
33+
ChannelListComponent,
34+
ChannelPreviewComponent,
35+
PaginatedListComponent,
36+
],
3237
providers: [
3338
{ provide: ChannelService, useValue: channelServiceMock },
3439
{
@@ -51,9 +56,9 @@ describe('ChannelListComponent', () => {
5156
queryChatdownContainer = () =>
5257
nativeElement.querySelector('[data-testid="chatdown-container"]');
5358
queryLoadingIndicator = () =>
54-
nativeElement.querySelector('[data-testid="loading-indicator"]');
55-
queryLoadMoreButton = () =>
56-
nativeElement.querySelector('[data-testid="load-more"]');
59+
nativeElement.querySelector(
60+
'[data-testid="loading-indicator-full-size"]'
61+
);
5762
queryEmptyIndicator = () =>
5863
nativeElement.querySelector(
5964
'[data-testid="empty-channel-list-indicator"]'
@@ -121,30 +126,38 @@ describe('ChannelListComponent', () => {
121126
expect(queryEmptyIndicator()).not.toBeNull();
122127
});
123128

124-
it('should display load more button', () => {
129+
it('should bind #hasMore', () => {
125130
const channels = generateMockChannels();
126131
channelServiceMock.channels$.next(channels);
132+
fixture.detectChanges();
133+
const paginatedListComponent = fixture.debugElement.query(
134+
By.directive(PaginatedListComponent)
135+
).componentInstance as PaginatedListComponent<Channel>;
127136
channelServiceMock.hasMoreChannels$.next(false);
128137
fixture.detectChanges();
129138

130-
expect(queryLoadMoreButton()).toBeNull();
139+
expect(paginatedListComponent.hasMore).toBeFalse();
131140

132141
channelServiceMock.hasMoreChannels$.next(true);
133142
fixture.detectChanges();
134143

135-
expect(queryLoadMoreButton()).not.toBeNull();
144+
expect(paginatedListComponent.hasMore).toBeTrue();
136145
});
137146

138-
it(`should load more channels, but shouldn't loading indicator`, () => {
147+
it(`should load more channels, but shouldn't display full-size loading indicator`, () => {
139148
const channels = generateMockChannels();
140149
channelServiceMock.channels$.next(channels);
141150
channelServiceMock.hasMoreChannels$.next(true);
142151
spyOn(channelServiceMock, 'loadMoreChannels');
143152
fixture.detectChanges();
144-
queryLoadMoreButton()?.click();
153+
const paginatedListComponent = fixture.debugElement.query(
154+
By.directive(PaginatedListComponent)
155+
).componentInstance as PaginatedListComponent<Channel>;
156+
paginatedListComponent.loadMore.emit();
157+
fixture.detectChanges();
145158

146-
expect(queryLoadingIndicator()).toBeNull();
147159
expect(channelServiceMock.loadMoreChannels).toHaveBeenCalledWith();
160+
expect(queryLoadingIndicator()).toBeNull();
148161
});
149162

150163
it('should apply dark/light theme', () => {

0 commit comments

Comments
 (0)