Ошибки выбора NgRx при попытке доступа к вложенным свойствам - PullRequest
2 голосов
/ 30 марта 2020

Я получаю TypeErrors при использовании функций выбора NgRx при доступе к вложенным свойствам.

У меня root мое хранилище сконфигурировано в app.module.ts, например:

StoreModule.forRoot({ app: appReducer }),

где приложение Редуктор это просто стандартный редуктор. Он правильно устанавливает состояние; Я могу видеть это в инструментах редукционного разработчика. Селекторы для некоторых вложенных свойств, которые содержат ошибки:

const getAppFeatureState = createFeatureSelector<IAppState>('app');

export const getAppConfig = createSelector(getAppFeatureState, state => {
  return state.appConfig.data;
});

export const getConfigControls = createSelector(getAppConfig, state => {
  console.log({ state }) // logs values from initial state
  return state.controls;
});

export const getConfigDropdowns = createSelector(
  getConfigControls,
  state => state.dropdowns,
);

Когда я подписываюсь на эти селекторы в app.compontent.ts, как это

ngOnInit() {
  this.store.dispatch(new appActions.LoadAppConfig());
  this.store
    .pipe(select(appSelectors.getConfigDropdowns))
    .subscribe(data => {
      console.log('OnInit Dropdowns Data: ', data);
    });
}

app.component.ts:31 ERROR TypeError: Cannot read property 'dropdowns' of null at app.selectors.ts:18

Когда я добавляю запись в селекторы выше по цепочке, я вижу, что единственными зарегистрированными элементами являются значения initialState, которые установлены в нуль. Я не думаю, что эта селекторная функция должна срабатывать, пока значение не изменится с первоначального значения. Но так как это не так, неудивительно, что я получаю эту ошибку, так как он пытается получить доступ к свойству с нулевым значением. Обязательно ли, чтобы initialState содержал полное дерево всех потенциальных будущих вложенных свойств, чтобы не сломать мои селекторы?

Как можно предотвратить срабатывание этого селектора, если его значение не изменилось?

Кроме того, правильно ли настроен StoreModule.forRoot? Меня несколько озадачивает тот факт, что при создании хранилища "root" создается ключ app в моем хранилище резервных копий параллельно хранилищам моих модулей, ie, хранилища модулей не находятся под app.

Редактировать:

Добавление общей структуры app.reducer.ts. Я использую immer , чтобы сократить шаблон, необходимый для обновления вложенных свойств, однако я попробовал этот редуктор также как более традиционный тип с оператором распространения по всему месту, и он работает идентично.

import produce from 'immer';

export const appReducer = produce(
  (
    draftState: rootStateModels.IAppState = initialState,
    action: AppActions,
  ) => {
    switch (action.type) {
      case AppActionTypes.LoadAppConfig: {
        draftState.appConfig.meta.isLoading = true;
        break;
      }
      /* more cases updating the properties accessed in problematic selectors */
      default: {
        return draftState; // I think this default block is unnecessary based on immer documentation
      }
    }
}

Редактировать: Добавить initialState:

const initialState: rootStateModels.IAppState = {
  user: null,
  appConfig: {
    meta: {isError: false, isLoading: false, isSuccess: false},
    data: {
      controls: {
        dropdowns: null,
      }
    },
  },
};

Ответы [ 4 ]

2 голосов
/ 07 апреля 2020

Вы можете использовать оператор filter, чтобы убедиться, что он выдает значения только для допустимых значений, и после этого вы можете использовать оператор pluck, чтобы выдать значение соответствующего вложенного свойства.

store.pipe(
  filter(value => state.feature.something),
  pluck('feature', 'something'),
)
2 голосов
/ 30 марта 2020

Поскольку вы обновили свой вопрос, ответом является https://www.learnrxjs.io/learn-rxjs/operators/filtering/distinctuntilchanged

, что позволяет выдавать значения только тогда, когда они были изменены.

store.pipe(
  map(state => state.feature.something),
  distinctUntilChanged(),
)

требуется state.feautre.something чтобы быть измененным.

Правильным способом было бы использовать createSelector функцию, которая возвращает запомненные селекторы, которая работает так же, как distinctUntilChanged.

1 голос
/ 07 апреля 2020

Метод dispatch - асин c. Итак:

ngOnInit() {
  this.store.dispatch(new appActions.LoadAppConfig());
  this.store
    .pipe(select(appSelectors.getConfigDropdowns))
    .subscribe(data => {
      console.log('OnInit Dropdowns Data: ', data);
    });
}

Здесь подписка выполняется быстрее, чем dispatch, поэтому выборка возвращается с нулевым значением из вашего исходного состояния. Просто проверьте это в селекторе или добавьте начальное состояние. Пример:

const getAppFeatureState = createFeatureSelector<IAppState>('app');

export const getAppConfig = createSelector(getAppFeatureState, state => {
  return state.appConfig.data;
});

export const getConfigControls = createSelector(getAppConfig, state => {
  console.log({ state }) // logs values from initial state
  return state.controls;
});

export const getConfigDropdowns = createSelector(
  getConfigControls,
  state => state ? state.dropdown : null,
);
0 голосов
/ 01 апреля 2020

Хорошо, я снова посмотрел код и обновил свой ответ.

Можете ли вы попробовать ниже приведенный пример.

this.store
  .pipe(
    // Here `isStarted` will be boolean value which will enable and disable selector. 
    //This can be derived from initial state, if null it wont go to next selector
    switchMap(data => {
      if (isStarted) {
        return never();
      } else {
        return of(data);
      }
    }),
    switchMap(data => select(appSelectors.getConfigDropdowns))
  )
  .subscribe(data => {
    console.log("OnInit Dropdowns Data: ", data);
  });
...