Выбранный фрагмент состоял из нескольких сущностей в NgRx Store
Я использую NgRx Entities для создания состояния дляlogs
'Редуктор состоял из лог-объектов: EntityState<Log>
, Затем я хочу подписаться из моего компонента Angular на несколько объектов Log. Если бы это был только один журнал, я бы использовал:
this.store$
.select(appStore => appStore.logs.entities[myLogId])
.subscribe(log => someExpensiveOperation())
Как я могу выбрать несколько сущностей и убедиться, что подписка срабатывает только один раз, если еще одна из этих сущностей была изменена?
1 ответ
Это немного сложнее, чем кажется. Есть два направления, которые я пробовал.
Первый - отфильтровать список, используя 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 - настройка селекторов?