So I am trying to build a list component reactively by angular and RxJS. the list has a lot of features like:
search service code:
private seachItemSubject = new BehaviorSubject<string>("");
private pageLimitSubject = new BehaviorSubject<number>(10);
private pageNumberSubject = new BehaviorSubject<number>(0);
private orderingSubject = new BehaviorSubject<ListOrder>({});
private filteringSubject = new BehaviorSubject<any>({});
searchItemAction$ = this.seachItemSubject.asObservable();
pageLimitAction$ = this.pageLimitSubject.asObservable();
pageNumberAction$ = this.pageNumberSubject.asObservable();
orderingAction$ = this.orderingSubject.asObservable();
filteringAction$ = this.filteringSubject.asObservable();
combination$ = Observable.combineLatest(
this.searchItemAction$,
this.pageLimitAction$,
this.pageNumberAction$,
this.orderingAction$,
this.filteringAction$
)
changeSeachItem(searchItem: string) {
this.seachItemSubject.next(searchItem);
}
changePageLimit(pageLimit: number) {
this.pageLimitSubject.next(pageLimit);
}
changePageNumber(pageNumber: number) {
this.pageNumberSubject.next(pageNumber);
}
changeOrdering(listOreder: ListOrder) {
this.orderingSubject.next(listOreder);
}
changeFilters(filters: any) {
this.filteringSubject.next(filters)
}
Components code
results$: Observable<MyResult> = this.listSearchService.combination$
.switchMap(([filterItem, pageLimit, pageNumber, ordering, filters]) => this.listService.query({
offset: pageNumber,
limit: pageLimit,
filter: filterItem,
order: ordering.order,
descending: ordering.descending,
...filters
}));
the problem that I am facing now is that I want the page number to be changed if the user used the filtering feature.
I think what I want is some sort of an Subject or an event that can be called whenever the user uses the filtering feature.
so it would be something like this.
applyFilters(filters: any) {
this.listSearchService.changePageNumber(0);
this.listSearchService.changeFilters(filters);
this.listSearchService.commit.next() -----> so it would change both then start the combine latest
console.log(filters)
}
I am not sure that that would be the right answer, so please tell me if you have a better answer.
Thanks
As you have discovered, you cannot use the original solution below as combineLatest
will fire both when changePageNumber
and changeFilters
are called from applyFilters
.
An alternative approach is to use a single state object implemented via the scan
operator, and update one or more properties in it at a time. This means that the service firing from it will only be called once per 'action'.
BTW you are currently storing your 'state' in each BehaviorSubject, and combining it.
class ListState {
searchItem: any = "";
pageLimit: any = 10;
pageNumber: any = 0;
ordering: any = "asc";
filtering: any = "";
}
const listStateUpdatesSubject = new BehaviorSubject<Partial<ListState>>(null);
const listStateUpdates$ = listStateUpdatesSubject.asObservable();
// fires once per state update, which can consist of multiple properties
const currListState$ = listStateUpdates$.pipe(
filter(update => !!update),
scan<Partial<ListState>, ListState>(
(acc, value) => ({ ...acc, ...value }), new ListState)
);
const callListService$ = currListState$.pipe(map(state => listService(state)));
function changeOrdering(listOreder: string) {
changeState({ ordering: listOreder });
}
function changeFilters(filters: any) {
changeState({ pageNumber: 0, filtering: filters }); // set both page number and filtering
}
// etc
Here is a working demo: https://stackblitz.com/edit/so-rxjs-filtering-2?file=index.ts, note that a call to changeFilters
results in the service being called once only.
Original Solution
Create a new observable that listens to filteringAction$
, perform the desired actions and use that new observable within the combineLatest
.
...
orderingAction$ = this.orderingSubject.asObservable();
filteringAction$ = this.filteringSubject.asObservable();
filterApplied$ = filteringAction$.pipe(map(filter => applyFilters(filter)));
applyFilters(filters: any) {
this.listSearchService.changePageNumber(0);
this.listSearchService.changeFilters(filters);
return filters;
}
combination$ = Observable.combineLatest(
this.searchItemAction$,
this.pageLimitAction$,
this.pageNumberAction$,
this.orderingAction$,
this.filterApplied$ // instead of filteringAction$
)