Skip to content

Commit cf73e5d

Browse files
committed
feat: add option for custom paginator
1 parent c655e6e commit cf73e5d

File tree

3 files changed

+105
-11
lines changed

3 files changed

+105
-11
lines changed

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,36 @@ describe('ChannelService', () => {
245245
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
246246
});
247247

248+
it('should set pagination options correctly if #customPaginator is provided', async () => {
249+
service.customPaginator = (
250+
channelQueryResult: Channel<DefaultStreamChatGenerics>[]
251+
) => {
252+
const lastChannel = channelQueryResult[channelQueryResult.length - 1];
253+
if (!lastChannel) {
254+
return {
255+
type: 'filter',
256+
paginationFilter: {},
257+
};
258+
} else {
259+
return {
260+
type: 'filter',
261+
paginationFilter: {
262+
cid: { $gte: lastChannel.cid },
263+
},
264+
};
265+
}
266+
};
267+
268+
await init();
269+
270+
expect(service['nextPageConfiguration']).toEqual({
271+
type: 'filter',
272+
paginationFilter: {
273+
cid: { $gte: jasmine.any(String) },
274+
},
275+
});
276+
});
277+
248278
it('should not set active channel if #shouldSetActiveChannel is false', async () => {
249279
const activeChannelSpy = jasmine.createSpy();
250280
service.activeChannel$.subscribe(activeChannelSpy);
@@ -375,7 +405,10 @@ describe('ChannelService', () => {
375405
await init();
376406

377407
// Check that offset is set properly after query
378-
expect(service['options']?.offset).toEqual(service.channels.length);
408+
expect(service['nextPageConfiguration']).toEqual({
409+
type: 'offset',
410+
offset: service.channels.length,
411+
});
379412

380413
mockChatClient.queryChannels.calls.reset();
381414
const existingChannel = service.channels[0];
@@ -390,6 +423,7 @@ describe('ChannelService', () => {
390423
jasmine.any(Object),
391424
jasmine.any(Object)
392425
);
426+
393427
expect(service.channels.length).toEqual(prevChannelCount + 1);
394428
});
395429

@@ -1952,14 +1986,14 @@ describe('ChannelService', () => {
19521986
});
19531987

19541988
it('should reset pagination options after reconnect', async () => {
1955-
await init(undefined, undefined, { offset: 20 });
1989+
await init(undefined, undefined, { limit: 20 });
19561990
mockChatClient.queryChannels.calls.reset();
19571991
events$.next({ eventType: 'connection.recovered' } as ClientEvent);
19581992

19591993
expect(mockChatClient.queryChannels).toHaveBeenCalledWith(
19601994
jasmine.any(Object),
19611995
jasmine.any(Object),
1962-
jasmine.objectContaining({ offset: 0 })
1996+
{ limit: 20, state: true, presence: true, watch: true, message_limit: 25 }
19631997
);
19641998
});
19651999

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

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
DefaultStreamChatGenerics,
3535
MessageInput,
3636
MessageReactionType,
37+
NextPageConfiguration,
3738
StreamMessage,
3839
} from './types';
3940

@@ -293,6 +294,10 @@ export class ChannelService<
293294
beforeUpdateMessage?: (
294295
message: StreamMessage<T>
295296
) => StreamMessage<T> | Promise<StreamMessage<T>>;
297+
/**
298+
* By default the SDK uses an offset based pagination, you can change/extend this by providing your own custom paginator method. It will be called with the result of the latest channel query.
299+
*/
300+
customPaginator?: (channelQueryResult: Channel<T>[]) => NextPageConfiguration;
296301
private channelsSubject = new BehaviorSubject<Channel<T>[] | undefined>(
297302
undefined
298303
);
@@ -374,6 +379,7 @@ export class ChannelService<
374379
this.activeParentMessageIdSubject.next(message?.id);
375380
};
376381
private dismissErrorNotification?: Function;
382+
private nextPageConfiguration?: NextPageConfiguration;
377383

378384
constructor(
379385
private chatClientService: ChatClientService<T>,
@@ -650,15 +656,14 @@ export class ChannelService<
650656
) {
651657
this.filters = filters;
652658
this.options = {
653-
offset: 0,
654659
limit: 25,
655660
state: true,
656661
presence: true,
657662
watch: true,
658663
message_limit: this.messagePageSize,
659664
...options,
660665
};
661-
this.sort = sort || { last_message_at: -1, updated_at: -1 };
666+
this.sort = sort || { last_message_at: -1 };
662667
this.shouldSetActiveChannel = shouldSetActiveChannel;
663668
this.clientEventsSubscription = this.chatClientService.events$.subscribe(
664669
(notification) => void this.handleNotification(notification)
@@ -1107,9 +1112,7 @@ export class ChannelService<
11071112
}
11081113
this.isStateRecoveryInProgress = true;
11091114
try {
1110-
if (this.options) {
1111-
this.options.offset = 0;
1112-
}
1115+
this.nextPageConfiguration = undefined;
11131116
// If channel list is not inited, we set the active channel
11141117
const shoulSetActiveChannel =
11151118
this.shouldSetActiveChannel &&
@@ -1377,10 +1380,16 @@ export class ChannelService<
13771380
await activeChannel?.stopTyping(parentId);
13781381
}
13791382

1383+
/**
1384+
* The current list of channels
1385+
*/
13801386
get channels() {
13811387
return this.channelsSubject.getValue() || [];
13821388
}
13831389

1390+
/**
1391+
* The current active channel
1392+
*/
13841393
get activeChannel() {
13851394
return this.activeChannelSubject.getValue() || undefined;
13861395
}
@@ -1480,12 +1489,32 @@ export class ChannelService<
14801489
) {
14811490
try {
14821491
this.channelQueryStateSubject.next({ state: 'in-progress' });
1492+
let filters: ChannelFilters<T>;
1493+
let options: ChannelOptions;
1494+
if (this.nextPageConfiguration) {
1495+
if (this.nextPageConfiguration.type === 'filter') {
1496+
filters = {
1497+
...this.filters!,
1498+
...this.nextPageConfiguration.paginationFilter,
1499+
};
1500+
options = this.options as ChannelOptions;
1501+
} else {
1502+
options = {
1503+
...this.options,
1504+
offset: this.nextPageConfiguration.offset,
1505+
};
1506+
filters = this.filters!;
1507+
}
1508+
} else {
1509+
filters = this.filters!;
1510+
options = this.options as ChannelOptions;
1511+
}
14831512
const channels = await this.chatClientService.chatClient.queryChannels(
1484-
this.filters!,
1513+
filters,
14851514
this.sort || {},
1486-
this.options
1515+
options
14871516
);
1488-
this.options!.offset = channels.length!;
1517+
this.setNextPageConfiguration(channels);
14891518
channels.forEach((c) => this.watchForChannelEvents(c));
14901519
const prevChannels = recoverState
14911520
? []
@@ -1898,4 +1927,18 @@ export class ChannelService<
18981927
void channel.markRead();
18991928
}
19001929
}
1930+
1931+
private setNextPageConfiguration(channelQueryResult: Channel<T>[]) {
1932+
if (this.customPaginator) {
1933+
this.nextPageConfiguration = this.customPaginator(channelQueryResult);
1934+
} else {
1935+
this.nextPageConfiguration = {
1936+
type: 'offset',
1937+
offset:
1938+
(this.nextPageConfiguration?.type === 'offset'
1939+
? this.nextPageConfiguration.offset
1940+
: 0) + channelQueryResult.length,
1941+
};
1942+
}
1943+
}
19011944
}

projects/stream-chat-angular/src/lib/types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Observable, Subject } from 'rxjs';
33
import type {
44
Attachment,
55
Channel,
6+
ChannelFilters,
67
ChannelMemberResponse,
78
CommandResponse,
89
Event,
@@ -360,3 +361,19 @@ export type MessageInput<
360361
quotedMessageId: string | undefined;
361362
customData: undefined | Partial<T['messageType']>;
362363
};
364+
365+
export type OffsetNextPageConfiguration = {
366+
type: 'offset';
367+
offset: number;
368+
};
369+
370+
export type FiltertNextPageConfiguration<
371+
T extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
372+
> = {
373+
type: 'filter';
374+
paginationFilter: ChannelFilters<T>;
375+
};
376+
377+
export type NextPageConfiguration =
378+
| OffsetNextPageConfiguration
379+
| FiltertNextPageConfiguration;

0 commit comments

Comments
 (0)