三态复选框的备份字段经常(重新)评估

时间:2018-05-24 16:09:17

标签: angular rxjs

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

  1. 状态“检查所有行”
  2. 状态“检查了一些但不是所有行”(中间)
  3. 状态“检查零行”
  4. 我说'天真',因为支持顶级复选框的字段经常被重新评估,我觉得我需要SubjectObservable字段来支持它。< / p>

    这是我当前实施的一个重复。

    1. ng new obstest --minimal(Angular 5 CLI)
    2. cd obstest
    3. ng generate service search并将其添加到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. import每个文件中的相关内容

    9. ng serve --open
    10. (重新)在打开控制台窗口的情况下加载应用程序:

      • 结果八个警告在控制台上(在我的实际应用中甚至更多),并在您选择/取消选择项目时保持流式传输,即使在可观察时改变没有关系/效果。
      • 预计两个警告在加载控制台上,两个相关更改到其他字段。

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

      我如何以视图可以数据绑定到它们的方式更改selectionCountselectionState,但只在需要时(重新)评估它们?

      任何人都可以在惯用的Angular和/或RxJs解决方案上启发我吗?

1 个答案:

答案 0 :(得分:2)

this.resultsnull开始,因此它在生命周期内有两个分配:第一个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可观察到的结果。我认为这被称为重/轻组件模式?这是一个非常流行的模式,以避免在任何地方处理可观察的事物,但我认为我的名字错了。

相关问题