Skip to content

Latest commit

 

History

History
203 lines (144 loc) · 4.98 KB

03-basic-runtime-optimizations.md

File metadata and controls

203 lines (144 loc) · 4.98 KB

Exercise 3: Basic runtime optimizations

In this exercise we will focus on basic runtime optimizations in Angular application.

Create a dirty checks component

First we create a component that will help us to debug change detection cycles in our application.

Your task is to create a DirtyChecksComponent which should serve as a performance debug utility. Whenever the application renders, it should increase a number in its template.

To do so, the component should bind a renders() function inside of the template which simply increase a local numeric variable everytime getting called.

For the template use a <code> tag with the class .dirty-checks.

hint for the template
<code class="dirty-checks">({{ renders() }})</code> 

Afterwards, use the component in the AppComponents template and serve the application.

show solution

Go to shared folder. Create dirty-checks.component.ts file with following content:

import { Component, ElementRef, NgModule } from "@angular/core";

@Component({
  selector: "dirty-checks",
  template: ` <code class="dirty-checks">({{ renders() }})</code> `,
})
export class DirtyChecksComponent {
  private _renders = 0;

  renders() {
    return ++this._renders;
  }
}

@NgModule({
  declarations: [DirtyChecksComponent],
  exports: [DirtyChecksComponent],
})
export class DirtyChecksComponentModule {}

Add import in app.module.ts:

// Exercise 3: Include dirty checks module import here.

import { DirtyChecksComponentModule } from "./shared/dirty-checks.component";

And include it.

// Exercise 3: Include dirty checks module
    DirtyChecksModule,

Add <dirty-checks> component in app.component.ts template:

template: `
<!-- Exercise 3: Add dirty checks here -->
<app-shell>
  <dirty-checks></dirty-checks>
  <router-outlet></router-outlet>
</app-shell>
`,

Evaluate initial state of the application

Serve your app and try to interact with the page. You will see counter always go up.

Use ChangeDetection.OnPush

By default all angular component using ChangeDetectionStrategy.Default. This means component will be checked and template bindings will be re-evaluated all the time which causing performance degradation. You should aim to use ChangeDetectionStrategy.OnPush in all your components.

Let's do one simple but significant change and make our app.component.ts use OnPush change detection:

// Exercise 3: Set app component CD to OnPush

@Component({
  selector: 'app-root',
  template: `
    <app-shell>
      <router-outlet></router-outlet>
    </app-shell>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})

Pro tips

  • You can generate components with OnPush by default if you add next content to angular.json schematics:
show solution
{
  "schematics": {
    "@schematics/angular": {
      "component": {
        "changeDetection": "OnPush"
      }
    }
  }
}
  • Use @angular-eslint plugins to force OnPush change detection.

Use trackBy with ngFor

trackBy is a crucial performance improvement when you iterate over non-primitive sets of data. We will optimize genres rendering in app-shell component.

In app-shell.component.ts import list model:

// Exercise 3: Add TMDBMovieGenreModel import here

import { TMDBMovieGenreModel } from "../data-access/api/model/movie-genre.model";

Create a trackByGenre function in the same file:

  // Exercise 3: Create trackBy function here

  trackByGenre(_: number, genre: TMDBMovieGenreModel) {
    return genre.name;
  }

Go to app-shell.component.html and add our function to template:

<!-- Exercise 3: Use trackBy here -->

<a
  [attr.data-uf]="'menu-gen-'+genre.id"
  *ngFor="let genre of genres$ | async; trackBy: trackByGenre;"
  class="navigation--link"
  [routerLink]="['/list', 'genre', genre.id]"
  routerLinkActive="active"
>
  <div class="navigation--menu-item">
    <svg-icon class="navigation--menu-item-icon" name="genre"></svg-icon>
    {{ genre.name }}
  </div>
</a>

Pro tip

  • You can force ngFor + trackBy with @angular-eslint linting

Optimize fetching

One of the most common operators for observables that trigger HTTP requests is switchMap. However switchMap can cause an over-fetching. For requests that will not change results quickly exhaustMap operator is a better option. This operator will ignore all higher observable emissions until it's observable finished.

This will help us improve TTI and TBT metrics.

Go to genre.state.ts, add import of exhaustMap and remove switchMap:

// Exercise 3: Replace switchMap with exhaustMap

import { exhaustMap } from "rxjs";

Go to genre.state.ts and switch to exhaustMap:

this.actions.refreshGenres$.pipe(
  // Exercise 3: Use exhaustMap here

  exhaustMap(this.genreResource.getGenres)
);