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
-
Google Developer Expert in Angular
-
Angular Oslo / PWA Oslo meetups organizer
-
ngVikings / ngCommunity 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
-
Good to have the message 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
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
Memoization
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.
- 2,063