从ngrx存储选择器仅获取不同的值

时间:2019-04-30 14:49:06

标签: rxjs ngrx ngrx-store

我有一个检查网格是否已加载的函数,如果没有,它将触发加载。但是目前,这最终会针对相同的Loaded值触发多次,因此它将多次调用相关动作。我的印象是,存储选择器默认情况下仅发出不同的(更改的)值?

我的功能

private gridLoaded(filters: FilteredListingInput): Observable<boolean> {
    return this.settings.states.Loaded.pipe(
        tap(loaded => {
            this.logService.debug(
                `Grid ID=<${
                    this.settings.id
                }> this.settings.states.Loaded state = ${loaded}`
            );

            // Now we get duplicate firings of this action.
            if (!loaded) {
                this.logService.debug(
                    `Grid Id=<${
                        this.settings.id
                    }> Dispatching action this.settings.stateActions.Read`
                );

                this.store.dispatch(
                    new this.settings.stateActions.Read(filters)
                );
            }
        }),
        filter(loaded => loaded),
        take(1)
    );
}

this.settings.states.Loaded是NgRx存储区中的选择器。 我得到的日志输出看起来像这样:

Grid ID=<grid-reviewItem> this.settings.states.Loaded state = false {ignoreIntercept: true}
Grid Id=<grid-reviewItem> Dispatching action this.settings.stateActions.Read {ignoreIntercept: true}
Grid ID=<grid-reviewItem> this.settings.states.Loaded state = true {ignoreIntercept: true}
Grid ID=<grid-reviewItem> Calling FilterClientSide action. Loaded=true {ignoreIntercept: true}
Grid ID=<grid-reviewItem> this.settings.states.Loaded state = true {ignoreIntercept: true}
Grid ID=<grid-reviewItem> Calling FilterClientSide action. Loaded=true {ignoreIntercept: true}
Grid ID=<grid-reviewItem> this.settings.states.Loaded state = true {ignoreIntercept: true}
Grid ID=<grid-reviewItem> Calling FilterClientSide action. Loaded=true {ignoreIntercept: true}

如何确保相关动作仅触发一次?

编辑-更新

选择代码:

export const getReviewItemsLoaded = createSelector(
    getReviewItemState,
    fromReviewItems.getReviewItemsLoaded
);

export const getReviewItemState = createSelector(
    fromFeature.getForecastState,
    (state: fromFeature.ForecastState) => {
        return state.reviewItems;
    }
);

export const getReviewItemsLoaded = (state: GridNgrxState<ReviewItemListDto>) =>
    state.loaded;

export interface GridNgrxState<TItemListDto> {
    allItems: TItemListDto[];
    filteredItems: TItemListDto[];
    totalCount: number;
    filters: FilteredListingInput;
    loaded: boolean;
    loading: boolean;
    selectedItems: TItemListDto[];
}

如您所见,我们只是获得了state.loaded属性,它是一个琐碎的选择器。

更改loading属性的

Reducers

export function loadItemsSuccessReducer(state: any, action: GridAction) {
    const data = action.payload;

    return {
        ...state,
        loading: false,
        loaded: true,
        totalCount: data.totalCount ? data.totalCount : data.items.length,
        allItems: data.items
    };
}

export function loadItemsReducer(state: any, action: GridAction) {
    return {
        ...state,
        loading: true,
        filters: action.payload
    };
}

export function loadItemsFailReducer(state: any, action: GridAction) {
    return {
        ...state,
        loading: false,
        loaded: false
    };
}

操作

export class LoadReviewItemsAction implements Action {
    readonly type = LOAD_REVIEWITEMS;
    constructor(public payload?: FilteredListingInput) {}
}

export class LoadReviewItemsFailAction implements Action {
    readonly type = LOAD_REVIEWITEMS_FAIL;
    constructor(public payload: any) {}
}

export class LoadReviewItemsSuccessAction implements Action {
    readonly type = LOAD_REVIEWITEMS_SUCCESS;
    constructor(public payload: PagedResultDtoOfReviewItemListDto) {}

效果

export class ReviewItemsEffects {
    constructor(
        private actions$: Actions,
        private reviewItemApi: ReviewItemApi
    ) {}

    @Effect()
    loadReviewItems$ = this.actions$
        .ofType(reviewItemActions.LOAD_REVIEWITEMS)
        .pipe(
            switchMap((action: reviewItemActions.LoadReviewItemsAction) => {
                return this.getDataFromApi(action.payload);
            })
        );

    /**
     * Retrieves and filters data from API
     */
    private getDataFromApi(filters: FilteredListingInput) {
        return this.reviewItemApi.getReviewItems(filters || {}).pipe(
            map(
                reviewItems =>
                    new reviewItemActions.LoadReviewItemsSuccessAction(
                        reviewItems
                    )
            ),
            catchError(error =>
                of(new reviewItemActions.LoadReviewItemsFailAction(error))
            )
        );
    }
}

1 个答案:

答案 0 :(得分:0)

我可以通过将gridLoaded方法重构为waitForGridLoaded并将其某些逻辑移出该方法来解决此问题。效果很好,但我无法解决tap(loaded => ...)逻辑为何被多次触发的原始​​问题。

现在相关的位看起来像这样(这似乎不是最好的解决方案):

private initializeLoadingState() {
    const loadingStateSubscription = this.settings.states.Loading.subscribe(
        loading => {
            this.loading = loading;
        }
    );
    this.addSubscription(loadingStateSubscription);
}

private initializeLoadedState() {
    const loadedStateSubscription = this.settings.states.Loaded.subscribe(
        loaded => {
            this.loaded = loaded;
        }
    );
    this.addSubscription(loadedStateSubscription);
}

onLazyLoad(event: LazyLoadEvent) {
    // Do nothing yet if we are expecting to set parent filters
    // but we have not provided any parent filter yet
    if (
        this.settings.features.ParentFilters &&
        (!this.parentFiltersOnClient ||
            !this.parentFiltersOnClient.length) &&
        (!this.parentFiltersOnServer || !this.parentFiltersOnServer.length)
    ) {
        return;
    }

    this.loadAndFilterItems(event);
}

private loadAndFilterItems(event: LazyLoadEvent) {
    if (this.settings.features.ClientSideCaching) {
        if (this.loaded) {
            // Load only once and filter client side
            this.store.dispatch(
                new this.settings.stateActions.FilterClientSide(
                    this.buildFilters(event, GridParentFilterTypes.Client)
                )
            );
        } else if (!this.loading) {
            // Start loading in from server side
            this.store.dispatch(
                new this.settings.stateActions.Read(
                    this.buildFilters(event, GridParentFilterTypes.Server)
                )
            );

            // When we have finished loading, apply any client side filters
            const gridLoadedSubscription = this.waitForGridLoaded().subscribe(
                loaded => {
                    if (loaded) {
                        this.store.dispatch(
                            new this.settings.stateActions.FilterClientSide(
                                this.buildFilters(
                                    event,
                                    GridParentFilterTypes.Client
                                )
                            )
                        );
                    }
                }
            );
            this.addSubscription(gridLoadedSubscription);
        }
    } else {
        this.store.dispatch(
            new this.settings.stateActions.Read(
                this.buildFilters(event, GridParentFilterTypes.Server)
            )
        );
    }
}

private waitForGridLoaded(): Observable<boolean> {
    return this.settings.states.Loaded.pipe(
        filter(loaded => loaded),
        take(1)
    );
}