DEV Community

Discussion on: 7 Deadly Sins of Angular

Collapse
raysuelzer profile image
Ray Suelzer

"Subscribing manually to an observable"
I somewhat disagree here. The async pipe will create a new subscription for each occurrence. In all but all but basic applications this is not ideal, you can end up with dozens of subscriptions to the same Observable, when if you subscribe manually and and set the property in the component, you will only need one. I've had massive performance increases on pages by being extremely careful about using the async pipe.
Additionally, as you pointed out, the async pipe does not handle errors. I have also run into very hard debug issues regarding component lifecycle and initialization of Observables when using the async pipe. I'd actually say avoid async pipes unless it's a really basic page or application. Instead, create a mixin which contains a disposer$ Subject that is called via ngOnDestroy and use the takeUntil(this.disposer$) on your observables in components. It's a clean pattern that avoids the performance hits and lost of functionality that come with the async pipe.

Example WithDisposableSubjectMixin

import { OnDestroy } from '@angular/core';
import { Constructor } from '@angular/material/core/common-behaviors/constructor';
import { Subject } from 'rxjs';

export function WithDisposableSubject<T extends Constructor<{}>>(Base: T = (class { } as any)) {
  return class extends Base implements OnDestroy {
    disposer$ = new Subject<any>();

    ngOnDestroy() {
      this.disposer$.next();
      this.disposer$.complete();
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

Then use in component

@Component({
  selector: 'app-entity-info-page',
  templateUrl: './entity-info.page.html',
})
export class EntityInfoPage extends
  WithDisposableSubject() implements OnInit {

   ngOnInit() {
     yourObservable$.pipe(takeUntil(this.disposer$))
   }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
layzee profile image
Lars Gyrup Brink Nielsen

Hi Ray,

Thank you for your feedback. Personally, I use container components and presentational components. This solves the issue with multiple subscriptions since the container component subscribes to the observable exactly once. It also solves the issue you mention with observable subscription time since the presentational component is not aware of observable values.

Collapse
layzee profile image
Lars Gyrup Brink Nielsen

Your solution comes with a footgun. If the takeUntil operation is placed in the wrong position of the observable pipeline, it will lead to unforeseen consequences such as memory leaks and duplicate side effects.

Collapse
raysuelzer profile image
Ray Suelzer

Not to mention, using an async pipe inside of an *ngIf can end up causing a massive number of subscriptions to be created an disposed if an element is toggled frequently.

<strong *ngIf="visible">{{totalCount$ | async}} Selected</strong>
Every time that visible switches from true to false, a brand new subscription is created/disposed to $totalCount. This can have serious performance impacts on an application, not mention it's very easy to end up with this type of code triggering a ton of network requests if totalCount$ is hitting the server.

I'm sure someone has written up a good article on the dangers of the async pipe.

Collapse
layzee profile image
Lars Gyrup Brink Nielsen • Edited on

I'm sure someone has written up a good article on the dangers of the async pipe.

I think AsyncPipe is grossly over-rated. Michael Hladky has talked a lot about what AsyncPipe is missing. He's even working on a range of replacements in the RxAngular (@rx-angular/*) packages.

Come to think of it, I interviewed him about this very topic.