Exploration of asyncPipe Source in Angular

It's the most elegant way to subscribe to observable, not only with a short syntax, but also with automatic unsubscribe.

The async pipeline is used to unpack asynchronous raw data.When it comes to asynchronous data, it's natural to think of observable s and promise s, where async subscribes to them, returns the latest value they've published, and marks components ready for change.When a component is destroyed, async automatically unsubscribes to prevent memory leaks.

Take a look at the implementation in its source code:

    @Pipe({name: 'async', pure: false})
    export class AsyncPipe implements OnDestroy, PipeTransform {
      //  Data rendered on a view engine, commonly referred to as presentation data
      private _latestValue: any = null;
      private _latestReturnedValue: any = null;
    
      private _subscription: SubscriptionLike|Promise<any>|null = null;
    	
    	// this.obj is the state of the observable we keep in the pipeline
      private _obj: Observable<any>|Promise<any>;
      private _strategy: SubscriptionStrategy = null !;
    
      constructor(private _ref: ChangeDetectorRef) {}
    
      ngOnDestroy(): void {
        if (this._subscription) {
          this._dispose();
        }
      }
    	
    	// obj is our incoming asynchronous data source
      transform(obj: Observable<any>|Promise<any>): any {
        if (!this._obj) {
          if (obj) {
    				console.log('Async: start subscription');
            this._subscribe(obj);
          }
    			console.log('Async: initial value = ' + this.lastestValue);
          this._latestReturnedValue = this._latestValue;
          return this._latestValue;
        }
    
    		// Guaranteed to be the same asynchronous data source, destroy the last subscription, subscribe to this asynchronous data when not the same observable
        if (obj !== this._obj) {
          this._dispose();
          return this.transform(obj as any);
        }
      }
    
      private _subscribe(obj: Observable<any>|Promise<any>|EventEmitter<any>): void {
        this._obj = obj;
        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();
        }
      }
    }

As you can see from the source code, when an async pipe is created, it calls transform() in each change detection cycle;

In transform(), a subscription to the asynchronous data source is created, which includes assigning the data source to the data source stored in the pipeline, rendering the change on the DOM, and marking the view as change to be detected.

If there is data, it will be judged whether the data source this time is the same as the last one, if not the same, then the last subscription will be destroyed and the asynchronous data will be subscribed to this time.

This allows us to reuse the latest values in the async pipeline and maintain a single subscription, which benefits the performance and maintainability of the application.

Reset and completion of async pipe

Reset: If the async data source changes, the async pipeline automatically unsubscribes and cleans up.Then subscribe to a new observable.The source code is as follows:

 if (obj !== this._obj) {
      this._dispose();
      return this.transform(obj as any);
    }

Complete: If the observable finishes and the publishing stops, the latest values will be rendered on the dom; however, the async pipeline triggers the transform until it is destroyed (component destruction, DOM destruction) in each change detection cycle; this causes redundant triggers and performance problems;

Optimize: Optimize using onPush

An asynchronous pipeline calls transform() during each change detection cycle because it is an impure pipeline.For each invocation of the transformation, the async pipeline performs a check to determine if the latest value has changed, and if not, returns early; see the source code:

if (ɵlooseIdentical(this._latestValue, this._latestReturnedValue)) {
      return this._latestReturnedValue;
}

We can avoid these redundant detection.So the async pipeline uses the parent component of ChangeDectionStrategey.onPush.When a component uses this policy, transfrom is invoked only when marked as a change;

When a value is published by an observable subscribed to by the async pipeline, it marks the view as pending change detection through markForCheck().This means that only when obervable publishes a value will he tell the component that it needs to be re-rendered.The source code is as follows:

private _subscribe(obj: Observable<any>|Promise<any>|EventEmitter<any>): void {
    this._obj = obj;
    this._strategy = this._selectStrategy(obj);
    this._subscription = this._strategy.createSubscription(
        obj, (value: Object) => this._updateLatestValue(obj, value));
}

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

Note: Although on-push can improve the performance of components, it must be used with caution and only when applicable. Incorrect use of on-push may result in bug s that the view does not update as expected;

summary

  • Use an asynchronous pipe to avoid redundant subscriptions; automatically unsubscribe to avoid memory leaks; or use takeUntil in rxjs, which also unsubscribes;
  • Use combineLatest in rxjs to group data into an async pipeline;
  • Use the async pipeline and on-push strategy only when necessary and with caution;

Tags: Front-end

Posted on Thu, 19 Mar 2020 01:10:05 -0400 by jackel15