With RxJS how to trigger observables by another action stream

大兔子大兔子 提交于 2021-01-28 07:10:37

问题


So I am trying to build a list component reactively by angular and RxJS. the list has a lot of features like:

  • search by word
  • change page number
  • change page size
  • filter by field(s)
  • sort

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


回答1:


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$
)


来源:https://stackoverflow.com/questions/63440800/with-rxjs-how-to-trigger-observables-by-another-action-stream

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!