Angular 记录 - Async pipe

2,856 阅读2分钟

async 概述


Angular 框架中集成了 Rxjs ,各类业务场景可以使用 Observable 进行处理。 关于 Observable , 可以参考 Angular 记录 - Observable 概述

由于 Observable 是惰性的,他需要被主动订阅去触发函数,在订阅之后还需要考虑 Observer 的回收问题,因此 Angular 为 Observable 这种异常操作专门设计了一个管道操作符 Async

async 使用示例


通过下面这个例子,可以对比使用 Async 管道所带来的诸多益处:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable, Subscription, fromEvent } from 'rxjs';
import { takeUntil, interval, map, map } from 'rxjs/operators';

@Component({
  selector: 'app-test',
  template: `CurrentTime: {{ time | date }}`
})
export class AppTestComponent implements OnInit, OnDestroy {
  time: Date;
  timeSubscription: Subscription;

  ngOnInit() {
    this.timeSubscription = Observable
      .interval(1000)
      .map(val => new Date())
      .subscribe(val => this.time = val);
  }

  ngOnDestroy() {
    this.timeSubscription.unsubscribe();
  }
}

在上面的代码片段中, 我们通过 interval 操作符,每 1000 ms获取一个 value 值,并使用 map 操作符将其映射为当前的时间戳。通过改变 time 变量来实现页面记时的功能。

在组件销毁阶段,我们对这个 Observer 进行回收处理。

这是相当多的样板代码,如果我们忘记取消订阅,就会有产生内存泄漏的风险

我们可以大大简化,这里 async 管道来实现相同的功能:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable, Subscription, fromEvent } from 'rxjs';
import { takeUntil, interval, map, map } from 'rxjs/operators';

@Component({
  selector: 'app-test',
  template: `Time: {{ time$ | async | date }}`
})
export class AppTestComponent {
  time$ = Observable
    .interval(1000)
    .map(val => new Date());
}

Async pipe 负责订阅和变化检测,以及在组件被销毁时取消订阅。通过一个操作符,帮助我们省去了大量的代码去处理 Observable 的繁琐问题。

在实际开发中,类似的问题都应该选择 async 管道去解决,保持代码简洁,但是对于在组合流,高阶流使用场景下,async 管道就无能为力了。

加上 $ 符,是 angular 开发的一个默认规则,所有包含 § 符的对象或者变量,我们会默认这个对象是一个 Observable 对象。

async 官方源码


Async 管道操作符会帮助我们主动订阅去订阅一个 Observable 对象,获取最新值,并触发变化检测。当组件销毁时候,AsyncPipe 还会帮助我们取消订阅,避免内存泄漏,在结合实际使用中,我们已经可以猜到它的实现原理了:

以下源码来自 Angular 仓库

@Pipe({name: 'async', pure: false})
export class AsyncPipe implements OnDestroy, PipeTransform {
  private _latestValue: any = null;
  private _latestReturnedValue: any = null;
  ...
    
  // 注入 ChangeDetectorRef 实例
  constructor(private _ref: ChangeDetectorRef) {}

  ngOnDestroy(): void {
    // 组件销毁时,调用 _dispose 方法
    if (this._subscription) {
      this._dispose();
    }
  }
  
  transform(obj: Observable<any>|Promise<any>|null|undefined): any {
    if (!this._obj) {
      if (obj) {
        // 对传入的 Observable 执行订阅操作
        this._subscribe(obj);
      }
      this._latestReturnedValue = this._latestValue;
      return this._latestValue;
    }
    ...
  }

  private _subscribe(obj: Observable<any>|Promise<any>|EventEmitter<any>): void {
    this._obj = obj;
    this._strategy = this._selectStrategy(obj);
    // 保存 subscription 对象,并执行 markForCheck 检测
    this._subscription = this._strategy.createSubscription(
        obj, (value: Object) => this._updateLatestValue(obj, value));
  }
    
 ...

  private _dispose(): void {
    this._strategy.dispose(this._subscription !);
    this._latestValue = null;
    this._latestReturnedValue = null;
    this._subscription = null;
    this._obj = null;
  }

  private _updateLatestValue(async: any, value: Object): void {
    if (async === this._obj) {
      this._latestValue = value;
      this._ref.markForCheck();
    }
  }
}

Async 管道符处理了在使用 Observable 时诸多麻烦事,因此在实际的业务场景中,我们应多使用 async 管道符来管理我们的数据。

async 更多使用示例


由于 Angular 很多业务流都可以用 Observable 解决。Async 操作符在项目中自然会大量的使用了,我们还可以结合很多场景来使用:

  • 在父子组件传参中使用:
    <home-risk-card [CardModel]="item | async"></home-risk-card>
  • 结合 *ngFor, *ngIf 等指令使用:
    <nz-col *ngFor="let item of riskData$ | async" [nzSpan]="8">
        <home-risk-card [CardModel]="item"></home-risk-card>
    </nz-col>
    
    <div class="app-test"  *ngIf="(riskData$ | async)?.length > 0"></div>
  • 结合变量来使用:
    <ng-container *ngIf="info$ | async as info">
      <h1>{{info.titile}}</h1>
      <div>
        {{info.content}}
      </div>
    </ng-container>
  • 结合 @ngrx/store 使用:
...
@Component({
  selector: 'app-test2',
  template: `
    <risk-info-list [books]="infoList$ | async"></risk-info-list>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TestComponent {
  infoList$: Observable<Info[]>;

  constructor(store: Store<AppState>) {
    this.infoList$ = store.select(getInfoListSelector);
  }
  ...
}

希望可以帮到你.

感谢您的阅读~