Select slice 由 NgRx Store 中的几个实体组成
Select slice consisted of several entities in NgRx Store
我使用 NgRx 实体为由日志实体组成的“logs
”减速器创建状态:EntityState<Log>
。然后我想从我的 Angular 组件订阅几个日志实体。如果只有一个日志,我会使用:
this.store$
.select(appStore => appStore.logs.entities[myLogId])
.subscribe(log => someExpensiveOperation())
如何 select 多个实体并确保订阅仅在其中一个实体发生更改时触发一次?
这比听起来要复杂一些。我尝试了两个方向。
首先是使用 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
选择器。这一个公开了一个对象,该对象可由实体的 id 索引。
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>
两者完成相同的事情,但方式不同。我没有进行任何类型的性能测试来确定哪个更好,但这可能取决于您选择的实体数量相对于列表的总大小。如果我不得不猜测,我会假设第一个性能更高。
关于选择结合多个切片的投影数据切片,您可以参考此答案:Denormalizing ngrx store- setting up selectors?
我使用 NgRx 实体为由日志实体组成的“logs
”减速器创建状态:EntityState<Log>
。然后我想从我的 Angular 组件订阅几个日志实体。如果只有一个日志,我会使用:
this.store$
.select(appStore => appStore.logs.entities[myLogId])
.subscribe(log => someExpensiveOperation())
如何 select 多个实体并确保订阅仅在其中一个实体发生更改时触发一次?
这比听起来要复杂一些。我尝试了两个方向。
首先是使用 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
选择器。这一个公开了一个对象,该对象可由实体的 id 索引。
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>
两者完成相同的事情,但方式不同。我没有进行任何类型的性能测试来确定哪个更好,但这可能取决于您选择的实体数量相对于列表的总大小。如果我不得不猜测,我会假设第一个性能更高。
关于选择结合多个切片的投影数据切片,您可以参考此答案:Denormalizing ngrx store- setting up selectors?