Search code examples
angularrxjsangular-materialasync-pipe

How to reduce the number async pipe subscriptions on template


I have this component StackBliz

Template:

<mat-sidenav-container class="sidenav-container">
  <mat-sidenav #drawer class="sidenav" fixedInViewport
      [attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
      [mode]="(isHandset$ | async) ? 'over' : 'side'"
      [opened]="(isHandset$ | async) === false">
    <mat-toolbar>Menu</mat-toolbar>
    <mat-nav-list>
      <a mat-list-item href="#">Link 1</a>
      <a mat-list-item href="#">Link 2</a>
      <a mat-list-item href="#">Link 3</a>
    </mat-nav-list>
  </mat-sidenav>
  <mat-sidenav-content>
    <mat-toolbar color="primary">
      <button
        type="button"
        aria-label="Toggle sidenav"
        mat-icon-button
        (click)="drawer.toggle()"
        *ngIf="isHandset$ | async">
        <mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
      </button>
      <span>dashboard-app</span>
    </mat-toolbar>
  </mat-sidenav-content>
</mat-sidenav-container>

Class:

export class NavigationComponent {

  isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
    .pipe(
      map(result => result.matches),
      shareReplay()
    );

  constructor(private breakpointObserver: BreakpointObserver) {}

}

I want to reduce the number of subscriptions on the page.

I try to wrap the content in to ng-container, but that didn't work.

First of all I've got an error in template in [opened]="isHandset === false", second of all ng-container brake the view, so even if I leave as it is [opened]="(isHandset$ | async) === false" nothing was displayed

<mat-sidenav-container class="sidenav-container">
    <ng-container *ngIf="isHandset$ | async as isHadset">
      <mat-sidenav #drawer class="sidenav" fixedInViewport
           [attr.role]="isHadset ? 'dialog' : 'navigation'"
           [mode]="isHadset ? 'over' : 'side'"
           [opened]="isHadset === false"> //got an error here saying this condition is always false because isHadset is always 'true'
    ...
           *ngIf="isHadset"
    ...
    
    
      </mat-sidenav-content>
    <ng-container>
</mat-sidenav-container>

Any ideas how to fix all two issues I've described above ?


Solution

  • Using *ngIf with a boolean emission is not ideal since it won't render the element if the value is false. However I see that initially your emission is an object. And since objects are truthy, you could ignore the mapping to boolean and use it directly with the *ngIf.

    I'd also recommend to wrap the entire container in the <ng-container> to preserve the DOM.

    Try the following

    Controller (*.ts)

    import { BreakpointObserver, Breakpoints, BreakpointState } from '@angular/cdk/layout';
    ...
    
    export class NavigationComponent {
      isHandset$: Observable<BreakpointState>;
    
      constructor(private breakpointObserver: BreakpointObserver) {
        this.isHandset$ = this.breakpointObserver.observe(Breakpoints.Handset);
      }
    }
    

    Template (*.html)

    <ng-container *ngIf="(isHandset$ | async) as result">
      <mat-sidenav-container class="sidenav-container">
        <mat-sidenav 
          #drawer 
          class="sidenav" 
          fixedInViewport 
          [attr.role]="(result.matches) ? 'dialog' : 'navigation'"
          [mode]="(result.matches) ? 'over' : 'side'" 
          [opened]="!result.matches"
        >
          <mat-toolbar>Menu</mat-toolbar>
          <mat-nav-list>
            <a mat-list-item href="#">Link 1</a>
            <a mat-list-item href="#">Link 2</a>
            <a mat-list-item href="#">Link 3</a>
          </mat-nav-list>
        </mat-sidenav>
        <mat-sidenav-content>
          <mat-toolbar color="primary">
            <button
              type="button"
              aria-label="Toggle sidenav"
              mat-icon-button
              (click)="drawer.toggle()"
              *ngIf="result.matches"
            >
              <mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
            </button>
            <span>dashboard-app</span>
          </mat-toolbar>
        </mat-sidenav-content>
      </mat-sidenav-container>
    </ng-container>
    

    I've modified your Stackblitz