Получите только различные значения из селектора магазина ngrx - PullRequest
0 голосов
/ 30 апреля 2019

У меня есть функция, которая проверяет, загружена ли сетка или нет, и если нет, она запускает загрузку.Но в настоящее время это приводит к запуску несколько раз для одного и того же значения 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))
            )
        );
    }
}

1 Ответ

0 голосов
/ 06 мая 2019

Мне удалось обойти эту проблему путем рефакторинга метода 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)
    );
}
...