首页 > 解决方案 > 仅从 ngrx 存储选择器中获取不同的值

问题描述

我有一个函数可以检查网格是否已加载,如果没有则触发加载。但目前这最终会为相同的值触发多次,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属性的减速器:

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))
            )
        );
    }
}

标签: rxjsngrxngrx-store

解决方案


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)
    );
}

推荐阅读