Optimized Angular apps:

Maxim Salnikov

Angular GDE

        smaller,

    faster,

better

How to create Angular apps that loaded and executed faster

To make the users (and us) happier

Maxim Salnikov

@webmaxru

  • Google Dev Expert, Microsoft MVP

  • PWA Oslo / PWA London meetups, PWA slack organizer

  • Mobile Era / ngVikings conferences organizer

Products from the future

UI Engineer at ForgeRock

  • How does it work?

  • How to start?

  • More info?

Web App Performance

Network bandwidth

Device performance

}

}

Generate smaller bundles

Optimize the code to display UI updates faster

Network performance strategy

  • Make overall bundle size smaller

  • Split bundle and download only what we need for the starting-up

  • Cache network responses for the future usage

Build the app using prod

$ ng build --prod
  • Using Ahead of Time compilation

  • Using Build Optimizer

  • Registering Angular Service Worker (if enabled)

  • Executes enableProdMode() via setting prod environment

Recipe #1

How to

$ ng build --prod --source-map
$ npx source-map-explorer main.bundle.js

Use lazy loading

  • Download the only code needed to start the app

  • Use CanLoad guard to mediate navigation

  • Preload all the modules (except the ones protected by CanLoad) by using PreloadAllModules strategy

  • 100% flexibility with your custom PreloadingStrategy

Recipe #2

How to

const appRoutes: Routes = [{
  path: 'tweets',
  loadChildren: 'tweets/tweets.module#TweetsModule'
}]
const tweetsRoutes: Routes = [{
  path: '',
  component: TweetFeedsComponent
}];

app-routing.module.ts

tweets/tweets-routing.module.ts

Preloading strategy

@NgModule({
  imports: [RouterModule.forRoot(appRoutes, {
    preloadingStrategy: PreloadAllModules
  })],
  exports: [RouterModule]
})

app-routing.module.ts

Add           features

  • Application shell

  • Possibility to optimize runtime requests

  • Web App Manifest

Recipe #3

$ ng add @angular/pwa
$ ng build --prod

}

  • Online: load all app assets from the cache

  • Offline: same as offline

App

Browser

App

Service worker

Cache

Browser

NGSW configuration file

src/ngsw-config.json

{
  "assetGroups": [
    Smart defaults for the app shell
  ],
  "dataGroups": [
    Settings for your API, etc
  ],
  ...
}

Runtime caching

ngsw-config.js / dataGroups

{
    "name": "myApi",
    "urls": [
      "/api/archive/**"
    ],






}
    "cacheConfig": {
      "strategy": "performance",
      "maxSize": 100,
      "maxAge": "365d"
    }

}

  • performance: cache-first

  • freshness: network-first

Keep in mind

  • App assets are downloaded twice during the first app start

  • Needed flow to notify user about the new app version

  • There are alternatives to Angular Service Worker

  • Can only be as a part of progressive enhancement strategy

Service Worker & Cache APIs support

Browser

Desktop

Mobile

OS

Consider server-side rendering

  • Better first-load experience

  • Social links with previews of a web site

  • Better for SEO

Recipe #4

Bootstrap

Load HTML

First meaningful paint

Bootstrap

Load HTML

First meaningful paint

No SSR

SSR

<app-root>Loading</app-root>

Loading

How to

Official

$ ng add @ng-toolkit/universal
$ npm run build:prod && npm run server
$ ng add @nguniversal/express-engine
$ npm run build:ssr && npm run serve:ssr

Community

Angular change detection (CD)

Data bindings

Update DOM

Event handlers

}

Change detection

}

UI render

Trigger

Ready

}

60FPS / 17ms

Code performance strategy

  • Make each change detection faster

  • Reduce number of change detections

  • Render as few as possible DOM changes

Use pure pipes instead of methods

<span>{{ relativeDate(tweet.createdAt) }}</span>

Calculated on every change detection

Recipe #5

<span>{{ tweet.createdAt | relativeDate }}</span>

Calculated only if the value was changed

Using method

Using pipe

How to

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
    name: 'relativeDate',
    pure: true
})
export class RelativeDatePipe implements PipeTransform {
  transform(date) {
    // Transformations...
    return newDate;
  }
}

Memoization

Memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again

Lodash Memoize

How to

import { memoize } from 'lodash-decorators';
...

@memoize()
relativeDate(date) {
  // Calculations
  return newDate;
}

Use OnPush CD strategy

Recipe #6

  • Every time

  • Any event happens

  • All components

}

By default

Use OnPush CD strategy

Recipe #6

  • Every time

  • Any event happens

  • All components

}

By default

Use OnPush CD strategy

Recipe #6

  • Every time

  • Any event happens

  • All components

}

By default

Use OnPush CD strategy

Recipe #6

  • Every time

  • Any event happens

  • All components

}

By default

How to

import { ..., ChangeDetectionStrategy } from '@angular/core';

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})
  • The @Input reference changes

  • An event occurres from the component or one of its children

  • Running change detection explicitly

  • Observable linked to the template via the async pipe emits a new value

ChangeDetectionStrategy.Default

ChangeDetectionStrategy.OnPush

Go for your own CD schedule

  • Disabling CD for the component completely

  • Running some code outside Angular CD

Recipe #7

How to

import { ..., ChangeDetectorRef } from '@angular/core';

class RealTimeList {
  constructor(private ref: ChangeDetectorRef) {
    ref.detach();
    setInterval(() => {
      this.ref.detectChanges();
    }, 1000);
  }
}
  • detach() / reattach()

  • detectChanges() / tick() / markForCheck()

How to

import { ..., NgZone } from '@angular/core';

class MyComponent implements OnChanges {

  constructor(private _ngZone: NgZone ) {}

  log() {
    this._ngZone.runOutsideAngular(() => {
      // Any code will not cause CD
    }}));
  }
}

Use trackBy in ngFor loops

  • DOM manipulations are expensive

  • Immutable practices generate a new DOM collection

Recipe #8

How to

export class TweetListComponent implements OnInit {
  ...

  trackById(index, tweet) {
      return tweet.id;
  }
}
<div *ngFor="let tweet of tweets; trackBy: trackById">

Angular App Performance

Networking

Code execution

}

}

  • Production mode

  • Lazy loading

  • Service worker

  • Server-side rendering

  • Using pure pipes

  • OnPush CD strategy

  • Custom CD strategy

  • Using trackBy

Thank you!

@webmaxru

Maxim Salnikov

Questions?

Optimized Angular apps: smaller, faster, better

By Maxim Salnikov

Optimized Angular apps: smaller, faster, better

Angular apps are getting more performant with each new version of the framework - thanks to the huge number of internal optimizations. But the main responsibility for the eventual app performance is on us, developers. Let's go through the main points on how we could help the framework to build and the browser to perform our app better.

  • 93
Loading comments...

More from Maxim Salnikov