我有一个'天真'的三行复选框选择实现多行,一个GMail和类似的应用程序。您可以选择单个行,并且有一个顶级三态复选框,表示:
我说'天真',因为支持顶级复选框的字段经常被重新评估,我觉得我需要Subject
或Observable
字段来支持它。< / p>
这是我当前实施的一个重复。
ng new obstest --minimal
(Angular 5 CLI)cd obstest
ng generate service search
并将其添加到app.module
providers
将此模拟方法添加到服务:
search(query: Observable<string>) {
// Fake search, ignore query for demo
return of<any[]>([
{ isSelected: false, id: 1, txt: 'item 1' },
{ isSelected: false, id: 2, txt: 'item 2' },
{ isSelected: false, id: 3, txt: 'item 3' },
]);
}
通常,这会使用HttpClient
从搜索API端点获取结果。
将其添加到app.component.ts
文件中:
enum TriState {
NothingSelected = '[ ]',
IntermediateSelection = '[-]',
EverythingSelected = '[X]',
}
将组件的装饰更改为:
@Component({
selector: 'app-root',
template: `
<div><input (keyup)="query$.next($event.target.value)"></div>
<div (click)="onMultiSelectChange()">{{selectionState}}</div>
<ul *ngFor="let item of results">
<li (click)='item.isSelected = !item.isSelected'>
{{item.isSelected ? '[X]' : '[ ]'}} {{item.txt}}
</li>
</ul>`,
})
用以下代码替换组件的代码:
export class AppComponent {
results: any[];
query$ = new Subject<string>();
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;
}
constructor (service: SearchService) {
service.search(of('fake query')).subscribe(r => this.results = r);
}
onMultiSelectChange() {
if (this.selectionState === TriState.EverythingSelected) {
this.results.forEach(r => r.isSelected = false);
} else {
this.results.forEach(r => r.isSelected = true);
}
}
}
import
每个文件中的相关内容
ng serve --open
(重新)在打开控制台窗口的情况下加载应用程序:
在KnockoutJS中,我知道如何通过使用“计算的可观察量”(可能是纯计算机)来做到这一点,我确信这可以用Angular 5+完成(可能在rxjs的帮助下? )。我只是不确定如何。
我如何以视图可以数据绑定到它们的方式更改selectionCount
和selectionState
,但只在需要时(重新)评估它们?
任何人都可以在惯用的Angular和/或RxJs解决方案上启发我吗?
答案 0 :(得分:2)
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没有对getter进行任何缓存。由于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
可观察到的结果。我认为这被称为重/轻组件模式?这是一个非常流行的模式,以避免在任何地方处理可观察的事物,但我认为我的名字错了。