Использование NgRx для интеграции данных объекта в компонент с локальным дубликатом

В моем приложении Angular у меня есть страница с набором фильтров, которые можно переключать, а затем сразу же отправлять.

Я построил это с помощью базовой службы, в которой есть объект фильтров (имя фильтра, указывающее на значение фильтра). Структура данных этой службы дублируется в локальную версию в компоненте (localFilters), которая обновляется по мере того, как пользователь нажимает флажки и т. Д. Если пользователь нажимает кнопку для отправки фильтров, локальные фильтры устанавливаются на глобальные фильтры. service, и если пользователь выходит без отправки, он не обновляет глобальную службу (и localFilters очищаются при выходе).

Однако у меня возникли проблемы с получением компонентов, которые используют эти данные для синхронизации со службой и другими компонентами, которые их используют, поэтому я решил попробовать NgRx, понимая, что этот шаблон на основе наблюдаемых более типичен для Angular и имеет ранее использовал Redux в ряде проектов React.

Однако у меня возникают серьезные проблемы с его настройкой по двум причинам:

  1. Шаблон, который я использовал раньше со службой, включал установку объекта localFilters при монтировании компонента на основе состояния объекта глобальных фильтров. Эти localFilters будут использоваться для определения начального состояния фильтров на странице и установлены глобально при отправке. Однако с наблюдаемым шаблоном, который использует NgRx, нет объекта filters для копирования - есть только наблюдаемый объект, и, следовательно, я не могу понять, как инициализировать объект localFilters. Как следствие, я не знаю, как установить состояние по умолчанию для различных компонентов фильтра из этого глобального объекта.

  2. По сути, я не знаю, как отображать значения фильтра в шаблоне (особенно это необходимо, если я не могу скопировать его данные в локальный объект). Базовые документы по началу работы с NgRx показывают, как включить числовое значение в шаблон с помощью канала async, но поскольку мои данные находятся в объектной форме, и я хочу передать значения этого объекта, этот метод не работает. Я пробовал следующие попытки на основе приведенной выше ссылки - filters$ | async (показывает [Object object]), filters$.someKey | async (ничего не показывает) и (filters$ | async).someKey (аналогично ничего не показывает).

По сути, большой вопрос заключается в том, как получить доступ к моментальному снимку объекта, хранящегося в состоянии NgRx, как для инициализации локального состояния этого компонента фильтров, так и для визуализации значений из объекта (или передачи этих значений) в шаблоне.

Или есть лучший образец, которому я должен следовать? (Хорошие примеры найти было трудно, и мы будем очень признательны).

Ниже приведена связка моего соответствующего кода.

Файл действий:

import { Action } from '@ngrx/store';

export enum ActionTypes {
  SetFilter = 'SetFilter',
  SetFilters = 'SetFilters',
  ClearFilters = 'ClearFilters',
}

export class SetFilter implements Action {
  readonly type = ActionTypes.SetFilter;
  constructor(public name: string, public value: any) {}
}

export class SetFilters implements Action {
  readonly type = ActionTypes.SetFilters;
  constructor(public filters: object) {}
}

export class ClearFilters implements Action {
  readonly type = ActionTypes.ClearFilters;
}

export type ActionsUnion = SetFilter | SetFilters | ClearFilters;

Файл-редукторы:

import * as FilterActions from './actions';

export interface State {
  filters: object
};

export const initialState: State = {
  filters: { wassup: 'true' } // testing initial state with some nonsense
};

export function reducer(state = initialState, action: FilterActions.ActionsUnion) {
  switch (action.type) {
    case FilterActions.ActionTypes.SetFilter: {
      return { ...state, [action.name]: action.value };
    }
    case FilterActions.ActionTypes.SetFilters: {
      return { ...state, ...action.filters };
    }
    case FilterActions.ActionTypes.ClearFilters: {
      return {};
    }
    default: return state;
  }
}

Сокращенный модуль приложения:

import { StoreModule } from '@ngrx/store';
import { reducer } from './ngrx/filters/reducer';

@NgModule({
  declarations: [...],
  imports: [
    ...,
    StoreModule.forRoot({ filters: reducer })
  ],
  ...
})

И сокращенная версия соответствующего файла component.ts:

@Component({
  selector: 'app-base-filter',
  templateUrl: './base-filter.component.html',
  styleUrls: ['./base-filter.component.scss']
})
export class BaseFilterComponent implements OnInit {
  /** Object with selected indices for given filter keys. */
  selectedIndices: any = {};

  /** Duplicate all filters locally, to save on submit and clear on cancel */
  localFilters: any = {};

  filters$: Observable<object>;

  constructor(private store: Store<{ filters: object }>) {
    this.filters$ = store.pipe(select('filters'));

    this.initLocalFilters();
  }

  ngOnInit() {}

  // This worked with the old filtersService model
  // But is obviously broken here, because I haven't been able to init
  // localFilters correctly.
  initLocalFilters () {
    this.localFilters = {};

    // Fill pre-selections from filter service
    ['this', 'is a list of', 'names of filters with', 'an array of options']
      .forEach((arrayKey) => {
        // The selected indices are used in the template to pass to child 
        // components and determine selected content.
        this.selectedIndices[arrayKey] = (this.localFilters[arrayKey] || [])
          .map(t => this[arrayKey].indexOf(t));
      });
  }
});

Между прочим, я пробовал некоторые из приведенных ниже в конструкторе компонентов выше:

// Doesn't throw an error, but doesn't enter the callback
this.store.select(data => { console.log(data) });

// Doesn't throw an error, but filter is undefined inside the loop
this.filters$ = store.pipe(select('filters'));
this.filters$.forEach(filter => { console.log(filter) });

Не уверен, возможен ли цикл по ключам / значениям фильтра.


person Sasha    schedule 07.12.2018    source источник


Ответы (1)


Я нашел ответ после просмотра этого немного устаревшего, но полезного примера видео (для тех, кто обнаружил, что документации значительно не хватает). Ничего особенного. Я просто не совсем понял, как интегрируется RxJs.

Все, что нужно было изменить - это код моего компонента:

import { Store, select } from '@ngrx/store';
import { Observable } from 'rxjs';

// These are local files. The @ format is just part
// of some path aliasing I've set up.
import { SetFilters } from '@store/filters/actions';
import { AppState } from '@store/reducers'; // Reducer interface

@Component({
  selector: 'app-base-filter',
  templateUrl: './base-filter.component.html',
  styleUrls: ['./base-filter.component.scss']
})
export class BaseFilterComponent implements OnInit {
  /** Object with selected indices for given. */
  selectedIndices: any = {};

  /** Duplicate all filters locally, to save on submit and clear on cancel */
  localFilters: any = {};

  /** Filters reducer */
  filters$: Observable<object>;

  constructor(private store: Store<AppState>) {
    this.filters$ = this.store.pipe(select('filters'));
    this.initLocalFilters();
  }

  ngOnInit() {}

  /**
   On component mount, clear any preexisting filters (if not dismounted),
   subscribe to filters in store, and initialize selectedIndices from filters.
  */
  initLocalFilters () {
    this.localFilters = {};

    this.filters$.subscribe(filters => {
      this.localFilters = { ...filters };
    });

    // Fill pre-selections from filter service
    ['this', 'is a list of', 'names of filters with', 'an array of options']
      .forEach((arrayKey) => {
        this.selectedIndices[arrayKey] = (this.localFilters[arrayKey] || [])
          .map(t => this[arrayKey].indexOf(t));
      });
  }

  ...

  submitFilters() {
    this.store.dispatch(new SetFilters(this.localFilters));
  }
}

Очевидно, что это не решает напрямую вопрос 2 (вопрос о значениях объектов шаблонов), но делает его спорным, потому что я могу легко дублировать содержимое хранилища локально и обновлять его при отправке.

person Sasha    schedule 07.12.2018