Tip

Simplify Angular Templates that Use Observables with View Models

Learn how to use view models in your Angular templates to handle multiple observables in a cleaner and more organized way, reducing the need for multiple async pipes.

When you work with observables in Angular, your templates can get a bit messy because you find yourself passing the async pipe everywhere and then need to keep track of it.

You can see in this template that we have a lot of async pipes and we need to keep track of them.

<!-- home.page.html -->
<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      {{ title$ | async }}
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content class="ion-padding">
  <p>
    I'm {{ name$ | async }} and I'm a {{ role$ | async }}, this is a random list of Pokemon:
  </p>
  <ion-list lines="none" class="ion-no-padding ion-no-margin">

    <ion-item *ngFor="let pokemon of (pokemon# | async)">
      <ion-label>
        {{ pokemon.name }}
      </ion-label>
    </ion-item>
  </ion-list>
</ion-content>

We can clean our template up a bit by using a view model in your template We'll do this by combining all the observables in your class into one single observable.

// home.page.ts
export class HomePage {
  private readonly http = inject(HttpClient);

  readonly title$ = of('Combining Observables');
  readonly name$ = of('Jorge Vergara');
  readonly role$ = of('Software Developer');

  readonly pokemon$ = this.http.get<PokeResponse>('https://pokeapi.co/api/v2/pokemon').pipe(
    map(response => response.results)
  )

  // add this observable to combine all the observables into one
  readonly vm$ = combineLatest([this.title$, this.name$, this.role$, this.pokemon$]).pipe(
    map(([title, name, role, pokemon]) => ({title, name, role, pokemon}))
  )
}

Now back in our view we can move everything into an ng container and reference the view model.

<ng-container xngIf="vm$ | async as vm">
  <ion-header [translucent]="true">
    <ion-toolbar>
      <ion-title>
        {{ vm.title }}
      </ion-title>
    </ion-toolbar>
  </ion-header>

  <ion-content class="ion-padding">
    <p>
      I'm {{ vm.name }} and I'm a {{ vm.role }}, this is a random list of Pokemon:
    </p>

    <ion-list lines="none" class="ion-no-padding ion-no-margin">
      <ion-item *ngFor="1let pokemon of vm.pokemon">
        <ion-label>
          {{ pokemon.name}}
        </ion-label>
      </ion-item>
    </ion-list>
  </ion-content>
</ng-container>