比如说,我有两个观察值要被模板使用,一个带有数据,另一个作为加载指示器:

public readonly data$ = this.backend.get<Data>(...).pipe(
  finalize((
    {
      this.isLoadingSubject$.next(1); 
      return () => this.isLoadingSubject$.next(-1);
    })()
);
private readonly isLoadingSubject$ = new Subject<number>();
public readonly isLoading$ = isLoadingSubject$.pipe(
    scan((acc, value) => acc + value, 0),
    map((i) => i > 0),
);

关于finalize的解释:我正在使用iLife在data$的管道被触发时开始加载,并在完成时结束加载.我使用的是递增或递减的数字,而不是布尔值,因为可能有多个同时请求(isLoading$机制由许多可观察的管道使用).

在我的模板中,我这样使用它:

<ng-container *ngIf="data$ | async as data">
  {{ data }}  
<button [disabled]="isLoading$ | async">some button</button>
</ng-container>

问题是对isLoading$的订阅很晚:这个订阅只在data$发出后发生,而前.next(+1)被忽略,因为没有订阅者.

我如何优雅地解决这个问题?

我try 过但不喜欢的变通方法:

  • 立即订阅isLoading$以使其成为热门——这似乎是浪费,在阅读代码时,不清楚为什么会这样做.因此,如果仅仅从代码上看不清楚它的用途,那么这似乎是一个糟糕的解决方法.
  • 重新排列模板,使isLoading$在第一个<ng-container>中,然后data$在第二个<ng-container>中-但我必须处理*ngIf在加载false时不渲染模板,因此我必须将其包装在一个对象中,这似乎再次浪费.而且,这会导致每次加载切换时都重新渲染所有内容,这很愚蠢.
  • 查看了publishReplay()运算符,但这已被弃用.
  • data$isLoading$包装在同一个<ng-container>中的一个对象中,但是当加载指示器改变时,整个模板就会被重新渲染,这非常浪费——我只想禁用一个按钮.

推荐答案

我认为最干净的处理方法是使用"workaournd#4";将两段数据放入单个对象(AKA a view model. This pattern is explained by Sander Elias in 100):

const INITIAL_STATE = {
  data      : undefined,
  isLoading : false,
};

@Component()
class MyComponent {
  private isLoadingSubject$ = new Subject<number>();

  private isLoading$ = isLoadingSubject$.pipe(
    scan((acc, value) => acc + value, 0),
    map(i => i > 0),
  );

  public vm$ = combineLatest({
    data      : this.getData()  .pipe(startWith(INITIAL_STATE.data)),
    isLoading : this.isLoading$ .pipe(startWith(INITIAL_STATE.isLoading))
  });

  private getData() {
    this.isLoadingSubject.next(1);

    return this.backend.get<Data>(...).pipe(
      finalize(() => this.isLoadingSubject.next(-1))
    );
  }
}
<ng-container *ngIf="vm$ | async as vm">
  {{ vm.data }}  
  <button [disabled]="vm.isLoading">some button</button>
</ng-container>

无需担心:

但是,只要加载指示器发生变化,整个模板就会重新呈现

在前面提到的视频中,这个问题在this part处提到.

当组合视图模型的任何部分发生更改时,确实会在组件上运行更改检测,但只有实际发生更改的元素才会重新提交;因此,如果只有isLoading个属性发生更改,那么UI中只有按钮会更新.data的绑定不会受到影响,因为它没有改变.

Angular相关问答推荐

如何在投影项目组周围添加包装元素?

如何在HTMLTITLE属性中使用Angular 显示特殊字符?

天使17中的倒计时器

Angular 单调服务已多次创建

nx serve正在忽略@angular/localize/init的导入"

如果Rxjs间隔请求仍在进行,则不应重置,但如果用户更改输入,则应重置

FromEvent不工作,本机事件绑定

ngRouterOutlet和组件生命周期使用自定义模板渲染的问题

区分material 手风琴

如果observableA在1秒前触发,则过滤掉observableB

Angular 2+ 一次性绑定

Observable.forkJoin 和数组参数

Angular 没有可用于依赖类型的模块工厂:ContextElementDependency

Angular CLI 自定义 webpack 配置

如何以编程方式触发`valueChanges`?

安装特定版本的 ng cli

在Angular2 Dart中设置路由和RouterLink的正确方法是什么

Angular 应用程序必须在新部署后清除缓存

将 [NgStyle] 与条件组合(if..else)

如何使用 @ngrx/store 获取 State 对象的当前值?