gpt4 book ai didi

angular - 三态复选框的支持字段被(重新)评估太频繁

转载 作者:太空狗 更新时间:2023-10-29 18:29:16 25 4
gpt4 key购买 nike

我有一个针对多行的三态复选框选择的“天真”实现,如 GMail 和类似应用程序。您可以选择单独的行,并且有一个顶级三态复选框指示:

  1. 声明“所有行都已选中”
  2. 说“检查了一些但不是所有的行”(中级)
  3. 声明“检查了零行”

我说“天真”,因为支持顶级复选框的字段被重新评估的方式太频繁了,我觉得我可能需要 SubjectObservable 字段改为支持它。

这是我当前实现的重现。

  1. ng new obstest --minimal(Angular 5 CLI)
  2. cd obstest
  3. ng 生成服务搜索 并将其添加到 app.module providers
  4. 将此模拟方法添加到服务中:

    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 端点获取结果。

  5. 将此添加到 app.component.ts 文件:

    enum TriState {
    NothingSelected = '[ ]',
    IntermediateSelection = '[-]',
    EverythingSelected = '[X]',
    }
  6. 将组件的装饰更改为:

    @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>`,
    })
  7. 将组件的代码替换为:

    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);
    }
    }
    }
  8. 导入每个文件中的相关内容

  9. ng serve --open

在控制台窗口打开的情况下(重新)加载应用程序,并且:

  • 结果:控制台上出现 8 个 警告(在我实际的应用程序中甚至更多),并且在您选择/取消选择项目时继续流式传输,即使在 observables 时也是如此没有关系/影响的更改。
  • 预期:两次 加载时控制台上的警告,两次相关对其他字段的更改。

在 KnockoutJS 中,我知道如何通过使用“计算可观察量”(可能是计算)来做到这一点,而且我确信这可以用 Angular 5+ 来完成(可能需要 rxjs 的帮助? ).我只是不确定如何。

我如何更改 selectionCountselectionState,使 View 可以数据绑定(bind)到它们,但仅在需要时才对它们进行(重新)评估?

谁能启发我惯用的 Angular 和/或 RxJs 解决方案?

最佳答案

this.resultsnull 开始,因此它在整个生命周期中有两次分配:首先是 null,然后是 [ ... 模拟数据 ... ] 您提供的数组。

调查你的 setter/getter :

  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$);
}
}

上面可能有bug,我没有测试,但希望它传达了这个想法。对于模板,您必须使用 | 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 可观察的结果。我认为这称为重/轻组件模式?这是一种非常流行的模式,可以避免在任何地方处理可观察对象,但我想我把名字弄错了。

关于angular - 三态复选框的支持字段被(重新)评估太频繁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50513785/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com