Post on 13-Apr-2017
SpeakersDavid Barreto
Solutions Architect @ Rangle.io
Blog: david-barreto.com
Andrew Smith
Solutions Architect @ Rangle.io
Agenda
1. Ahead of Time Compilation2. Lazy Loading3. Change Detection4. Memory Leaks5. Server Side Rendering
Rangle Academy
Goal and Structure:
Program to share knowledge within the companyIt follows a "workshop" structureUsually 2 hours longCovers hard and soft skills
Some workshops available:
WebpackReactReact NativeGoogle AnalyticsUnit TestingIntroduction to Payment GatewaysContinuous Delivery to ProductionConflict Management
About the Demo App
Characteristics:
Built using Angular 2.4.1
Uses Angular CLI beta-26
Redux store with ngrx
Tachyons for CSS
Server side rendering with Angular Universal
All the numbers shown are based on:
Low end device (5x CPU slowdown)
Good 3G connection (Latency: 40ms, DL: 1.5 Mbps, UL: 750 Kbps)
Compilation Modes
Just in Time Compilation (JiT):
Compilation performed in the browser at run timeBigger bundle size (includes the compiler)Takes longer to boot the app
$ ng serve --prod
Ahead of Time Compilation (AoT):
Compilation performed in the server at build timeSmaller bundle size (doesn't include the compiler)The app boots faster
$ ng serve --prod --aot
JiT vs AoT in Demo App (Prod + Gzip)
CSS files are included in the "other js files"
File Size (JiT) Size (AoT)
main.bundle.js 6.4 KB 23.9 KB
vendor.bundle.js 255 KB 158 KB
other js files 48.7 KB 49.6 KB
Total Download 306 KB 231.5 KB
AoT goals (from the ):docs
Faster rendering => Components already compiledFewer async request => Inline external HTML and CSSSmaller bundle size => No compiler shippedDetect template errors => Because they canBetter security => Prevents script injection attack
Boot Time Comparison
Event Time (JiT) Time (AoT)
DOM Content Loaded 5.44 s 3.25 s
Load 5.46 s 3.27 s
FMP 5.49 s 3.30 s
DOM Content Loaded:
The browser has finished parsing the DOMjQuery nostalgia => $(document).ready()
Load: All the assets has been downloaded
First Meaningful Paint (FMP):
When the user is able to see the app "live" for the first time
(Show browser profile for both modes)
What is Lazy Loading?
Ability to load modules on demand => Useful to reduce the app startup time
(Compare branches no-lazy-loading vs normal-lazy-loading )
Bundle Sizes Comparison (Prod + AoT)
File Size (No LL) Size (LL)
main.bundle.js 23.9 KB 17.4 KB
vendor.bundle.js 158 KB 158 KB
other js files 49.6 KB 49.6 KB
Initial Download 231.5 KB 225 KB
0.chunk.js - 9.1 KB
Total Download 231.5 KB 234.1 KB
Webpack creates a "chunk" for every lazy loaded moduleThe file 0.chunk.js is loaded when the user navigates to adminThe initial download size is smaller with LLThe total size over time is bigger with LL because of Webpack async loadingThe effect of LL start to be noticeable when the app grows
Boot Time Comparison (Prod + AoT)
Event Time (No LL) Time (LL)
DOM Content Loaded 3.25 s 3.11 s
Load 3.27 s 3.25 s
FMP 3.30 s 3.16 s
Not much difference for an small appJust one lazy loaded module with a couple of componentsThe impact is noticeable for big apps
Enable Preloading
Define the property preloadingStrategy in the root module routing
import { PreloadAllModules } from '@angular/router'; export const routes: Routes = [ ... ]; @NgModule({ imports: [ RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }) ], exports: [ RouterModule ], }) export class AppRoutingModule {}
What's Change Detection (CD)?
It's a mechanism to keep our "models" in sync with our "views"
Change detection is fired when...
The user interacts with the app (click, submit, etc.)An async event is completed (setTimeout, promise, observable)
When CD is fired, Angular will check every component starting from the top once.
Change Detection Strategy: OnPush
Angular offers 2 strategies:
Default: Check the entire component when CD is firedOnPush: Check only relevant subtrees when CD is fired
OnPush Requirements:
Component inputs ( @Input ) need to be immutable objects
@Component({ selector: 'rio-workshop', templateUrl: './workshop.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) export class WorkshopComponent { @Input() workshop: Workshop; @Input() isSummary = false; }
View Example
Summary
What to do?
Apply the OnPush change detection on every component*Never mutate an object or array, always create a a new reference ( )blog
// Don't let addPerson = (person: Person): void => { people.push(person); }; // Do let addPerson = (people: Person[], person: Person): Person[] => { return [ ...people, person ]; };
Benefits:
Fewer checks of your components during Change DetectionImproved overall app performance
What Causes Memory Leaks in Angular?
Main Source => Subscriptions to observables never closed
@Injectable() export class WorkshopService { getAll(): Observable<Workshop[]> { ... } }
@Component({ selector: 'rio-workshop-list', template: ` <div *ngFor="let workshop of workshops"> {{ workshop.title }} </div>` }) export class WorkshopListComponent implements OnInit { ... ngOnInit() { this.service.getAll().subscribe(workshops => this.workshops = workshops); } }
Manually Closing Connections
Before the element is destroyed, close the connection
@Component({ selector: 'rio-workshop-list', template: ` <div *ngFor="let workshop of workshops"> {{ workshop.title }} </div>` }) export class WorkshopListComponent implements OnInit, OnDestroy { ... ngOnInit() { this.subscription = this.service.getAll() .subscribe(workshops => this.workshops = workshops); } ngOnDestroy() { this.subscription.unsubscribe(); } }
The async Pipe
It closes the connection automatically when the component is destroyed
@Component({ selector: 'rio-workshop-list', template: ` <div *ngFor="let workshop of workshops$ | async"> {{ workshop.title }} </div>` }) export class WorkshopListComponent implements OnInit { ngOnInit() { this.workshops$ = this.service.getAll(); } }
This is the recommended way of dealing with Observables in your template!
The Http Service
Every method of the http services ( get , post , etc.) returns an observableThose observables emit only one value and the connection is closed automaticallyThey won't cause memory leak issues
@Component({ selector: 'rio-workshop-list', template: ` <div *ngFor="let workshop of workshops"> {{ workshop.title }} </div>` }) export class WorkshopListComponent implements OnInit { ... ngOnInit() { this.http.get('some-url') .map(data => data.json()) .subscribe(workshops => this.workshops = workshops); } }
Emit a Limited Number of Values
RxJs provides operators to close the connection automaticallyExamples: first() and take(n)
This won't cause memory leak issues even if getAll emits multiple values
@Component({ selector: 'rio-workshop-list', template: ` <div *ngFor="let workshop of workshops"> {{ workshop.title }} </div>` }) export class WorkshopListComponent implements OnInit { ngOnInit() { this.service.getAll().first() .subscribe(workshops => this.workshops = workshops); } }
Angular Universal
Provides the ability to pre-render your application on the server
Much faster time to first paint
Enables better SEO
Enables content preview on social networks
Fallback support for older browsers
Use the as the base of your applicationuniversal-starter
What's Included
Suite of polyfills for the server
Server rendering layer
Preboot - replays your user's interactions after Angular has bootstrapped
State Rehydration - Don't lose your place when the application loads
Boot Time Comparison (Client vs Server)
Both environments include previous AoT and Lazy Loading enhancements
Event Time (Client) Time (Server)
DOM Content Loaded 3.11 s 411 ms
Load 3.25 s 2.88 s
FMP 3.16 s ~440 ms
*Times are on mobile over 3G
Universal Caveats
Cannot directly access the DOM
constructor(element: ElementRef, renderer: Renderer) { renderer.setElementStyle(element.nativeElement, ‘font-size’, ‘x-large’); }
Current solutions only cover Express and ASP.NET servers
Project will be migrated into the core Angular repo for v4
Performance Changes
Event JiT AoT Lazy Loading SSR
DOM Content Loaded 5.44 s 3.25 s 3.11 s 411 ms
Load 5.46 s 3.27 s 3.25 s 2.88 s
FMP 5.46 s 3.30 s 3.16 s ~440 ms
% Improvement (FMP) 39.6% 4.3% 86.1%
*Times are on mobile over 3G
Thank You
Slides: https://github.com/rangle/angular-performance-meetup