this.results
начинается с null
, поэтому у него есть два назначения в течение жизненного цикла: сначала null
, затем предоставленный вами массив [ ... mock data ... ]
.
Исследование ваших получателей:
public get selectionCount() {
console.warn('Getting count at', new Date().toISOString());
return this.results.filter(r => r.isSelected).length;
}
public get selectionState() {
console.warn('Getting state at', new Date().toISOString());
if (this.selectionCount === 0) { return TriState.NothingSelected; }
if (this.selectionCount === this.results.length) { return TriState.EverythingSelected; }
return TriState.IntermediateSelection;
}
Когда вызывается selectionState
, он вызывает предупреждение, затем дважды вызывает selectionCount
, так что три вызова вызываются за вызов selectionState
.Angular не выполняет кэширование получателей.Они вызываются дважды в течение всего жизненного цикла из-за двух назначений this.results
, что составляет шесть предупреждений о нагрузке.Я не уверен, откуда берутся оставшиеся два.
Более RxJS способ написать этот класс - избежать мутаций состояний и делать все с наблюдаемым, что-то вроде:
export class AppComponent {
results$: Observable<any[]>;
selections$ = new BehaviorSubject<boolean[]>([]);
selectionCount$: Observable<number>;
selectionState$: Observable<TriState>;
query$ = new Subject<string>();
constructor (service: SearchService) {
this.results$ = service.search(of('fake query')).pipe(shareReplay(1));
this.selectionCount$ = combineLatest(this.results$, this.selections$).pipe(
map(([results, selections]) => results.filter((result, i) => selections[i])),
map(results => results.length),
);
this.selectionState$ = of(TriState.IntermediateSelection).pipe(concat(this.results.pipe(map(
results => {
if (this.selectionCount === 0) { return TriState.NothingSelected; }
if (this.selectionCount === this.results.length) { return TriState.EverythingSelected; }
}))));
}
toggle(i) {
selections$.value[i] = !selections$.value[i];
selections$.next(selections$.value);
}
toggleAll() {
combineLatest(this.selectionState$, this.results$).pipe(
first(),
map(([state, results]) => {
return results.map(() => state === TriState.EverythingSelected);
}))
.subscribe(this.selections$);
}
}
Вероятно, есть ошибки выше, я не проверял это, но, надеюсь, это передает идею.Для шаблона вам нужно будет использовать канал | async
, что-то вроде:
@Component({
selector: 'app-root',
template: `
<div><input (keyup)="query$.next($event.target.value)"></div>
<div (click)="toggleAll()">{{selectionState | async}}</div>
<ul *ngFor="let item of results | async">
<li (click)='toggle($index)'>
{{item.isSelected ? '[X]' : '[ ]'}} {{item.txt}}
</li>
</ul>`,
})
К сожалению, Angular не обеспечивает какого-либо стандартизированного управления состояниями, такого как Redux, для реализации этого шаблона, поэтомувы либо должны быть достаточно дисциплинированными, чтобы делать это самостоятельно, либо не бояться дополнительных вызовов.
В качестве альтернативы, вы также можете иметь компонент-оболочку, который обрабатывает Observable
и связанное с ним состояние, а не шаблон и иметь дочерний элемент.компонент только визуализирует состояние.Это позволит избежать всех преобразований состояния, и вам нужно будет только async
получить наблюдаемые результаты.Я думаю, что это называется узором тяжелых / легких компонентов?Это довольно популярная модель, позволяющая избегать повсеместного обращения с наблюдаемыми, однако я думаю, что я неправильно понял название.