Skip to content

Commit

Permalink
Merge pull request #641 from GetStream/channel-list-infinite-scroll
Browse files Browse the repository at this point in the history
Channel list infinite scroll
  • Loading branch information
szuperaz authored Sep 17, 2024
2 parents c5224e9 + 4d4dbab commit 215c8e1
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 68 deletions.
12 changes: 3 additions & 9 deletions docusaurus/docs/Angular/code-examples/responsive-layout.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -204,20 +204,14 @@ Lastly, implement auto close behavior for the channel list:

```html
<stream-channel-list
(click)="closeMenu()"
(click)="closeMenu($event)"
class="channel-list menu-{{ isMenuOpen ? 'open' : 'close' }}"
></stream-channel-list>
```

```ts
closeMenu() {
let isChannelQueryInProgress = false;
this.channelService.channelQueryState$.pipe(take(1)).subscribe((state) => {
if (state?.state === 'in-progress') {
isChannelQueryInProgress = true;
}
});
if (!isChannelQueryInProgress) {
closeMenu(event: Event) {
if ((event.target as HTMLElement).closest('stream-channel-preview')) {
this.isMenuOpen = false;
}
}
Expand Down
4 changes: 2 additions & 2 deletions projects/sample-app/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
class="channel-list menu-{{ isMenuOpen ? 'open' : 'close' }} thread-{{
isThreadOpen ? 'open' : 'close'
}}"
(click)="closeMenu()"
(click)="closeMenu($event)"
></stream-channel-list>
<stream-channel class="channel" (click)="closeMenu()">
<stream-channel class="channel" (click)="closeMenu($event)">
<stream-channel-header>
<button
class="menu-button"
Expand Down
12 changes: 3 additions & 9 deletions projects/sample-app/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
ViewChild,
} from '@angular/core';
import { Observable } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { map } from 'rxjs/operators';
import {
ChatClientService,
ChannelService,
Expand Down Expand Up @@ -77,14 +77,8 @@ export class AppComponent implements AfterViewInit {
);
}

closeMenu() {
let isChannelQueryInProgress = false;
this.channelService.channelQueryState$.pipe(take(1)).subscribe((state) => {
if (state?.state === 'in-progress') {
isChannelQueryInProgress = true;
}
});
if (!isChannelQueryInProgress) {
closeMenu(event: Event) {
if ((event.target as HTMLElement).closest('stream-channel-preview')) {
this.isMenuOpen = false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,44 +30,29 @@
>
{{ "streamChat.You have no channels currently" | translate }}
</p>
<ng-container
*ngFor="let channel of channels$ | async; trackBy: trackByChannelId"
<stream-paginated-list
[items]="(channels$ | async) ?? []"
[hasMore]="(hasMoreChannels$ | async) ?? false"
[isLoading]="isLoadingMoreChannels"
(loadMore)="loadMoreChannels()"
>
<ng-template #defaultTemplate let-channelInput="channel">
<stream-channel-preview
data-testclass="channel-preview"
[channel]="channelInput"
></stream-channel-preview>
<ng-template let-channel="item">
<ng-template #defaultTemplate let-channelInput="channel">
<stream-channel-preview
data-testclass="channel-preview"
[channel]="channelInput"
></stream-channel-preview>
</ng-template>
<div>
<ng-container
*ngTemplateOutlet="
customChannelPreviewTemplate || defaultTemplate;
context: { channel: channel }
"
></ng-container>
</div>
</ng-template>
<div>
<ng-container
*ngTemplateOutlet="
customChannelPreviewTemplate || defaultTemplate;
context: { channel: channel }
"
></ng-container>
</div>
</ng-container>
<div
*ngIf="hasMoreChannels$ | async"
class="str-chat__load-more-button"
data-testid="load-more"
(click)="loadMoreChannels()"
(keyup.enter)="loadMoreChannels()"
>
<button
class="str-chat__load-more-button__button str-chat__cta-button"
data-testid="load-more-button"
[disabled]="isLoadingMoreChannels"
>
<span *ngIf="!isLoadingMoreChannels; else loadingIndicator">{{
"Load more" | translate
}}</span>
<ng-template #loadingIndicator
><stream-loading-indicator-placeholder></stream-loading-indicator-placeholder
></ng-template>
</button>
</div>
</stream-paginated-list>
<ng-content select="[channel-list-bottom]"></ng-content>
</div>
</div>
Expand All @@ -89,7 +74,10 @@
</ng-template>

<ng-template #loadingChannels>
<div data-testid="loading-indicator" class="str-chat__loading-channels">
<div
data-testid="loading-indicator-full-size"
class="str-chat__loading-channels"
>
<ng-container *ngTemplateOutlet="loadingChannel"></ng-container>
<ng-container *ngTemplateOutlet="loadingChannel"></ng-container>
<ng-container *ngTemplateOutlet="loadingChannel"></ng-container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
import { ThemeService } from '../theme.service';
import { ChannelListComponent } from './channel-list.component';
import { Subject, of } from 'rxjs';
import { PaginatedListComponent } from '../paginated-list/paginated-list.component';
import { Channel } from 'stream-chat';

describe('ChannelListComponent', () => {
let channelServiceMock: MockChannelService;
Expand All @@ -21,14 +23,17 @@ describe('ChannelListComponent', () => {
let queryChannels: () => ChannelPreviewComponent[];
let queryChatdownContainer: () => HTMLElement | null;
let queryLoadingIndicator: () => HTMLElement | null;
let queryLoadMoreButton: () => HTMLElement | null;
let queryEmptyIndicator: () => HTMLElement | null;

beforeEach(() => {
channelServiceMock = mockChannelService();
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [ChannelListComponent, ChannelPreviewComponent],
declarations: [
ChannelListComponent,
ChannelPreviewComponent,
PaginatedListComponent,
],
providers: [
{ provide: ChannelService, useValue: channelServiceMock },
{
Expand All @@ -51,9 +56,9 @@ describe('ChannelListComponent', () => {
queryChatdownContainer = () =>
nativeElement.querySelector('[data-testid="chatdown-container"]');
queryLoadingIndicator = () =>
nativeElement.querySelector('[data-testid="loading-indicator"]');
queryLoadMoreButton = () =>
nativeElement.querySelector('[data-testid="load-more"]');
nativeElement.querySelector(
'[data-testid="loading-indicator-full-size"]'
);
queryEmptyIndicator = () =>
nativeElement.querySelector(
'[data-testid="empty-channel-list-indicator"]'
Expand Down Expand Up @@ -121,30 +126,38 @@ describe('ChannelListComponent', () => {
expect(queryEmptyIndicator()).not.toBeNull();
});

it('should display load more button', () => {
it('should bind #hasMore', () => {
const channels = generateMockChannels();
channelServiceMock.channels$.next(channels);
fixture.detectChanges();
const paginatedListComponent = fixture.debugElement.query(
By.directive(PaginatedListComponent)
).componentInstance as PaginatedListComponent<Channel>;
channelServiceMock.hasMoreChannels$.next(false);
fixture.detectChanges();

expect(queryLoadMoreButton()).toBeNull();
expect(paginatedListComponent.hasMore).toBeFalse();

channelServiceMock.hasMoreChannels$.next(true);
fixture.detectChanges();

expect(queryLoadMoreButton()).not.toBeNull();
expect(paginatedListComponent.hasMore).toBeTrue();
});

it(`should load more channels, but shouldn't loading indicator`, () => {
it(`should load more channels, but shouldn't display full-size loading indicator`, () => {
const channels = generateMockChannels();
channelServiceMock.channels$.next(channels);
channelServiceMock.hasMoreChannels$.next(true);
spyOn(channelServiceMock, 'loadMoreChannels');
fixture.detectChanges();
queryLoadMoreButton()?.click();
const paginatedListComponent = fixture.debugElement.query(
By.directive(PaginatedListComponent)
).componentInstance as PaginatedListComponent<Channel>;
paginatedListComponent.loadMore.emit();
fixture.detectChanges();

expect(queryLoadingIndicator()).toBeNull();
expect(channelServiceMock.loadMoreChannels).toHaveBeenCalledWith();
expect(queryLoadingIndicator()).toBeNull();
});

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

0 comments on commit 215c8e1

Please sign in to comment.