Understanding Angular’s Async Pipe: Condensed Angular Experiences – Part 1

This is the first article of the mini-series 'Condensed Angular Experiences'. We will explore concepts in Angular that seem simple but are very powerful and critical for the application architecture.

In this article:

Max Marschall is engineer consultant at Thinktecture with focus on Angular and 2D/3D visualization.
  1. Understanding Angular’s Async pipe
  2. What is the hype with Angular’s OnPush change detection?
  3. Interlude: About smart and representational components
  4. Smart and presentational components with Angular
  5. Different approaches to complex and advanced forms in Angular (coming soon)
Every Angular developer knows the async pipe. It is almost always present when writing components and using some observables or promises. This article will explore how it works and for what it is suitable. You can find more information on RxJs, Observables, and how to use them properly here. Angular’s async pipe is a tool to resolve the value of a subscribable in the template. A subscribable can be an Observable, an EventEmitter, or a Promise. The pipe listens for promises to resolve and observables and event emitters to emit values. Let’s take a look at how we can profit from using the async pipe.

Where Do We Profit From Using the Async Pipe?

As simple as the async pipe function sounds, it opens the door to more readable code, fewer unintended memory leaks, and better performance. It makes the code more readable by moving the variable declaration and usage of the template.

					<some-component [inputValue]="someObservable$ | async"></some-component>


Further, it handles the subscription for us and, more importantly, unsubscribes from the subscribable if the host component is destroyed.

But the most critical advantage plays into our hands in conjunction with the OnPush change detection strategy. Immutable values bound to a component input automatically refresh the corresponding component’s view, but that does not apply to member variables that are used in the template. The async pipe marks the host view as dirty and ready to check if the value was updated.

The OnPush change detection strategy is discussed in detail in the next article of this series.

How Does the Async Pipe Work?

When taking a look at the source code of the async pipe, we can go through its functions step by step and analyze how it works.

The pipe takes a subscribable, subscribes internally to it, stores, and re-emits its value. It also handles the teardown if you swap the underlying object. You can see that behavior in the code below. That is the original implementation, starting with the pipes entry point, the transform function.

					// overload
transform<T>(obj: Subscribable<T>|Promise<T>|null|undefined): T|null {
    if (!this._obj) {
      if (obj) {
      return this._latestValue;

    if (obj !== this._obj) {
      return this.transform(obj);

    return this._latestValue;

Handling Subscribable and Promises Distinctly

Note that Angular distinguish between a promise and other types. Both types are explicitly handled to their specifications. Both receive the callback that updates the internal value.

					private _subscribe(obj: Subscribable<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 _selectStrategy(obj: Subscribable<any>|Promise<any>|EventEmitter<any>): any {
  if (ɵisPromise(obj)) {
    return _promiseStrategy;

  if (ɵisSubscribable(obj)) {
    return _subscribableStrategy;

  throw invalidPipeArgumentError(AsyncPipe, obj);

The promise variant resolves the Promise via the then function. The Subscribable variant calls the subscribe function instead and returns the subscription. That way, it is possible to unsubscribe later on destroy or calling it directly while swapping the underlying subscription. While the SubscribableStrategy must unsubscribe, the PromiseStrategy does nothing on dispose and on destroy because it doesn’t have to. Anyway, the method is called in both cases to be safe and fulfill the API.

The async pipe also unsubscribes inside the on-destroy lifecycle hook to prevent memory leaks. As explained in the first block, the pipe handles the teardown itself by calling the dispose function. Which will call dispose on the specifically selected strategy.

					ngOnDestroy(): void {
  if (this._subscription) {

private _dispose(): void {
  this._latestValue = null;
  this._subscription = null;
  this._obj = null;

This is a simplified schema of the whole process. Notice that the update of the inner value happens asynchronously to the rest.

Hooking Into the Change Detection

But the best part is yet to come. The pipe also helps us with the change detection. It can become cumbersome and complex to manage view updates using the OnPush change detection strategy. But the pipe automatically calls the markForCheck() method on updates for us. Afterward, Angular runs the change detection on the next cycle until reaching the host component itself.

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

Now that we know how the built-in async pipe works, I’ve created a simple demo app that shows the different scenarios and effects of the async pipe in conjunction with the OnPush change detection strategy.

The Async Pipe in Action

Using the pipe is as simple as this:

					<some-component [inputValue]="someObservable$ | async"></some-component>

It takes the observable and maps its value to the input.

Alternatively, we could resolve it in an *ngIf and access the object’s properties.

					<ng-container *ngIf="someObservable$ | async as resolved">
    <some-component [inputValue]="resolved.value"></some-component>
    <other-component [data]="resolved.dataset"></other-component>

All that works fine until we must handle mutable objects. Angular’s change detection works by comparing the identity of two objects obj1 === obj2 and Object.is(), disregarding internal changes. However, this does not work with a mutable object, but how does it come then that it seems to work in the demo application (as seen on the lower right card)?

As described, the pipe marks the host component to be checked in the next change detection cycle. That check will go through the component’s view and looks for changes. That way, even, at first, undetected changes will be updated.

If you can’t use the async pipe for some reason, you could, for instance, resolve observables and promises in your component.

An Alternative to the Async Pipe

As alternatives to the async pipe, you can resolve the observable or promise in your component.

					this.valueSub = this.numberService.value$.subscribe(x => {
    this.value = x;


But keep in mind, you have to make sure not to cause any memory leaks by unsubscribing from the observable.

					ngOnDestroy(): void {
    // ...

When using OnPush you also have to make sure that the view is updated correctly. For this, we can call the markForCheck() method on the ChangeDetectorRef and notify it about changes.

					this.valueSub = this.numberService.value$.subscribe(x => {


This article explores the async pipe, how it works, what it is used for, and examines its source code in detail. We have seen how to use it and provided examples, demos, and alternatives. Now it is up to you to decide where to use it in your projects and when. This conclusion might help you further with that decision.

As seen in the examples above, we don’t have to use Angular’s build-in async pipe. We can resolve promises and subscribable ourselves. But we must handle the subscription clean-up and make sure the view is updated. The latter part is vital in conjunction with the OnPush change detection strategy.

By using the async pipe, we let Angular take on the heavy lifting for us. It simplifies the components and reduces written code by moving the complexity into the framework. It also unifies the way we handle Promises, Observables, EventEmitter, and Subjects; generally Subscribables – creating a pleasant dev experience at the cost of increasing the required framework knowledge. Isn’t it what we want a Framework for to hide complexity and for a good consistent API? But we don’t have to add extra imports to our components and write repetitive code. We don’t have to worry about changes when using the OnPush change detection strategy if we use immutable objects or cut the components accordingly.


Current articles, screencasts and interviews by our experts

Don’t miss any content on Angular, .NET Core, Blazor, Azure, and Kubernetes and sign up for our free monthly dev newsletter.

EN Newsletter Anmeldung (#7)
Related Articles
One of the more pragmatic ways to get going on the current AI hype, and to get some value out of it, is by leveraging semantic search. This is, in itself, a relatively simple concept: You have a bunch of documents and want to find the correct one based on a given query. The semantic part now allows you to find the correct document based on the meaning of its contents, in contrast to simply finding words or parts of words in it like we usually do with lexical search. In our last projects, we gathered some experience with search bots, and with this article, I'd love to share our insights with you.
If you previously wanted to integrate view transitions into your Angular application, this was only possible in a very cumbersome way that needed a lot of detailed knowledge about Angular internals. Now, Angular 17 introduced a feature to integrate the View Transition API with the router. In this two-part series, we will look at how to leverage the feature for route transitions and how we could use it for single-page animations.
.NET 8 brings Native AOT to ASP.NET Core, but many frameworks and libraries rely on unbound reflection internally and thus cannot support this scenario yet. This is true for ORMs, too: EF Core and Dapper will only bring full support for Native AOT in later releases. In this post, we will implement a database access layer with Sessions using the Humble Object pattern to get a similar developer experience. We will use Npgsql as a plain ADO.NET provider targeting PostgreSQL.