Выбранный фрагмент состоял из нескольких объектов в NgRx Store - PullRequest
0 голосов
/ 26 апреля 2018

Я использую объекты NgRx для создания состояния для редуктора 'logs', состоящего из объектов журнала: EntityState<Log>.Затем я хочу подписаться от моего компонента Angular на несколько сущностей Log.Если бы это был только один Журнал, я бы использовал:

this.store$
  .select(appStore => appStore.logs.entities[myLogId])
  .subscribe(log => someExpensiveOperation())

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

1 Ответ

0 голосов
/ 26 апреля 2018

Это немного сложнее, чем кажется.Есть два направления, которые я пробовал.

Первый - отфильтровать список с помощью оператора map.Карта будет вызываться всякий раз, когда какая-либо сущность изменяется в списке, поэтому вам нужно иметь оператора после нее, чтобы игнорировать дубликаты.Поскольку карта будет создавать новый массив каждый раз, вы не можете использовать стандартный оператор distinct* для его фильтрации.Я создал пользовательский оператор с именем distinctElements, который в основном равен distinctUntilChanged, но он выполняет проверку ссылок на элементы массива, а не на сам массив.В этом примере я предполагаю, что вы используете селектор selectAll, сгенерированный адаптером сущностей.Он предоставляет массив всех сущностей.

const { Observable, BehaviorSubject } = rxjs;
const { startWith, pairwise, filter, map, tap } = rxjs.operators;

function distinctElements(){
    return (source) => source.pipe(
        startWith(null),
        pairwise(),
        filter(([a, b]) => a == null || a.length !== b.length || a.some(x => !b.includes(x))),
        map(([a, b]) => b)
    );
};

let state = [
  { id: 1, value: 'a' },
  { id: 2, value: 'b' },
  { id: 3, value: 'c' }
];
const store$ = new BehaviorSubject(state);

const ids = [1, 3];
store$.pipe(
  map(entities => entities.filter(entity => ids.includes(entity.id))),
  distinctElements()
).subscribe((entities) => { console.log('next', entities); });

setTimeout(() => {
  state = [...state, { id: 4, value: 'd' }];
  console.log('add entity (4)');
  store$.next(state);
}, 10);

setTimeout(() => {
  state[0].value = 'aa';
  state = [{...state[0]}, ...state.slice(1)];
  console.log('update entity (1)');
  store$.next(state);
}, 1000);

setTimeout(() => {
  state = [...state.slice(0, 1), ...state.slice(2)];
  console.log('remove entity (2)');
  store$.next(state);
}, 2000);
<script src="https://unpkg.com/rxjs@rc/bundles/rxjs.umd.min.js"></script>

Второй вариант - создать отдельные наблюдаемые для каждой сущности и сделать combineLatest для всех них.В этом примере я предполагаю, что вы используете селектор selectEntities, сгенерированный адаптером сущностей.Этот объект предоставляет объект, который индексируется по идентификатору объекта.

const { Observable, BehaviorSubject, combineLatest, timer } = rxjs;
const { startWith, pairwise, filter, map, debounce, distinctUntilChanged } = rxjs.operators;

function distinctElements(){
    return (source) => source.pipe(
        startWith(null),
        pairwise(),
        filter(([a, b]) => a == null || a.length !== b.length || a.some(x => !b.includes(x))),
        map(([a, b]) => b)
    );
};

let state = {
  1: { id: 1, value: 'a' },
  2: { id: 2, value: 'b' },
  3: { id: 3, value: 'c' }
};
const store$ = new BehaviorSubject(state);

const ids = [1, 3];
combineLatest(
  ids.map(id => store$.pipe(
    map(entities => entities[id]),
    distinctUntilChanged()
  ))
).pipe(
  debounce(() => timer(0))
).subscribe((entities) => { console.log('next', entities); });

setTimeout(() => {
  state = { ...state, 4: { id: 4, value: 'd' } };
  console.log('add entity (4)');
  store$.next(state);
}, 10);

setTimeout(() => {
  state[1].value = 'aa';
  state = { ...state, 1: {...state[1]} };
  console.log('update entity (1)');
  store$.next(state);
}, 1000);

setTimeout(() => {
  state = { ...state };
  delete state[2];
  console.log('remove entity (2)');
  store$.next(state);
}, 2000);
<script src="https://unpkg.com/rxjs@rc/bundles/rxjs.umd.min.js"></script>

Оба выполняют одно и то же, но по-разному.Я не проводил никакого теста производительности, чтобы увидеть, какой из них лучше, но он, вероятно, зависит от количества выбранных вами объектов относительно общего размера списка.Если бы мне пришлось угадывать, я бы предположил, что первое является более производительным.

В отношении выбора среза проецируемых данных, объединяющего несколько срезов, вы могли бы сослаться на этот ответ: Денормализация селекторов настройки хранилища ngrx?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...