Angular Performance Checklist

Angular Performance Checklist

High performance applications always bring better user engagement and experience. We often implicitly judge the quality of given application by it’s initial load time and responsiveness.

In the world of the single-page applications we usually have to transfer huge amount of resources over the wire which dramatically impacts the initial load time. On top of that, performing change detection over the entire component tree, corresponding to a complex UI, often causes frame drops because of heavy computations happening in the main thread.

In the first part of this talk we’re going explain essential practices that can help us reduce the initial load time of our Angular applications. In it’s second part, we’ll discuss different techniques which can improve the runtime performance of our application in order to help us achieve rendering with 60fps!

82bafb0432ce4ccc9dcc26f94d5fe5bc?s=128

Minko Gechev

May 30, 2017
Tweet

Transcript

  1. 4.
  2. 5.
  3. 6.
  4. 7.
  5. 13.

    twitter.com/mgechev Observer Pattern ‣ Push based ‣ State triggers an

    event on change ‣ View handles the event and updates
  6. 14.

    // state.ts class State extends EventEmitter {...} const state =

    new State({ todos: [] }); state.todos.push('Buy milk'); state.emit(Change) // view.ts class View { // more code update(state) { // visualize todos } } const view = new View(); state.on(Change, _ => view.update(state));
  7. 15.

    // state.ts class State extends EventEmitter {...} const state =

    new State({ todos: [] }); state.todos.push('Buy milk'); state.emit(Change) // view.ts class View { // more code update(state) { // visualize todos } } const view = new View(); state.on(Change, _ => view.update(state));
  8. 16.

    twitter.com/mgechev Virtual Dom ‣ State renders in a “virtual view”

    ‣ Diff between the last two versions of the “virtual view” ‣ Diff applied to the view
  9. 23.
  10. 24.
  11. 26.

    twitter.com/mgechev Dirty Checking ‣ After each event callback ‣ After

    each network message ‣ After each timeout callback ‣ …
  12. 27.

    sample.ts fetch(url) .then(r => r.json()) .then(data => { // mutate

    the state detectChanges(); }); timeout(() => { // mutate the state detectChanges(); }, 1000); btn.addEventListener('click', () => { // mutate the state detectChanges(); }); const detectChanges = _ => { if (state !== prevState) { updateView(state); } };
  13. 28.

    sample.ts fetch(url) .then(r => r.json()) .then(data => { // mutate

    the state detectChanges(); }); timeout(() => { // mutate the state detectChanges(); }, 1000); btn.addEventListener('click', () => { // mutate the state detectChanges(); }); const detectChanges = _ => { if (state !== prevState) { updateView(state); } };
  14. 29.

    sample.ts fetch(url) .then(r => r.json()) .then(data => { // mutate

    the state detectChanges(); }); timeout(() => { // mutate the state detectChanges(); }, 1000); btn.addEventListener('click', () => { // mutate the state detectChanges(); }); const detectChanges = _ => { if (state !== prevState) { updateView(state); } };
  15. 30.

    sample.ts fetch(url) .then(r => r.json()) .then(data => { // mutate

    the state detectChanges(); }); timeout(() => { // mutate the state detectChanges(); }, 1000); btn.addEventListener('click', () => { // mutate the state detectChanges(); }); const detectChanges = _ => { if (state !== prevState) { updateView(state); } };
  16. 31.

    sample.ts fetch(url) .then(r => r.json()) .then(data => { // mutate

    the state detectChanges(); }); timeout(() => { // mutate the state detectChanges(); }, 1000); btn.addEventListener('click', () => { // mutate the state detectChanges(); }); const detectChanges = _ => { if (state !== prevState) { updateView(state); } };
  17. 33.

    zone.ts (pseudo code) https://goo.gl/j1Osvu const orig = Node.prototype.addEventListener; Node.prototype.addEventListener =

    function (old, f) { let cb = () => { old(); zone.afterInvoke(); }; orig.call(this, cb, f); };
  18. 35.

    application_ref.ts ... tick(): void { ... try { this._views.forEach((view) =>

    view.detectChanges()); if (this._enforceNoNewChanges) { this._views.forEach((view) => view.checkNoChanges()); } } catch (e) { ... } } ...
  19. 36.

    application_ref.ts ... tick(): void { ... try { this._views.forEach((view) =>

    view.detectChanges()); if (this._enforceNoNewChanges) { this._views.forEach((view) => view.checkNoChanges()); } } catch (e) { ... } } ...
  20. 48.

    application_ref.ts ... tick(): void { ... try { this._views.forEach((view) =>

    view.detectChanges()); if (this._enforceNoNewChanges) { this._views.forEach((view) => view.checkNoChanges()); } } catch (e) { ... } } ...
  21. 52.

    twitter.com/mgechev Always invoke enableProdMode in our production applications Default behavior

    for the production builds of Angular CLI, angular seed Practice #2
  22. 53.

    twitter.com/mgechev How Angular Updates The View ‣ Checks for changes

    in the state ‣ Applies only required updates
  23. 56.
  24. 58.

    twitter.com/mgechev Use Ahead-of-Time compilation in production Default behavior for the

    production builds of Angular CLI, angular seed Practice #3
  25. 59.

    twitter.com/mgechev Use Ahead-of-Time compilation in production Default behavior for the

    production builds of Angular CLI, angular seed Practice #3 everywhere
  26. 62.

    twitter.com/mgechev ChangeDetectorRef ‣ Detach the change detector ‣ Check for

    changes manually ‣ Re-attach the change detector Allows us to:
  27. 63.

    @Component({ selector: 'todos-cmp', template: ` <todo *ngFor="let item of items"

    [todo]="item"> </todo> ` }) class TodosComponent { items: Todo[] = []; constructor(private ref: ChangeDetectorRef) { ref.detach(); } addItem(item: Todo) { this.items.push(item); this.ref.detectChanges(); } }
  28. 64.

    @Component({ selector: 'todos-cmp', template: ` <todo *ngFor="let item of items"

    [todo]="item"> </todo> ` }) class TodosComponent { items: Todo[] = []; constructor(private ref: ChangeDetectorRef) { ref.detach(); } addItem(item: Todo) { this.items.push(item); this.ref.detectChanges(); } }
  29. 65.

    @Component({ selector: 'todos-cmp', template: ` <todo *ngFor="let item of items"

    [todo]="item"> </todo> ` }) class TodosComponent { items: Todo[] = []; constructor(private ref: ChangeDetectorRef) { ref.detach(); } addItem(item: Todo) { this.items.push(item); this.ref.detectChanges(); } }
  30. 76.

    @Component({ selector: 'todos-cmp', template: `...` }) class TodosComponent { _items:

    Todo[] = []; constructor(private ref: ChangeDetectorRef) { ref.detach(); } @Input() set items(items: Todo[]) { this._items = items; this.ref.detectChanges(); } get items() { return this._items; } }
  31. 77.

    @Component({ selector: 'todos-cmp', template: `...` }) class TodosComponent { _items:

    Todo[] = []; constructor(private ref: ChangeDetectorRef) { ref.detach(); } @Input() set items(items: Todo[]) { this._items = items; this.ref.detectChanges(); } get items() { return this._items; } }
  32. 78.

    @Component({ selector: 'todos-cmp', template: `...` }) class TodosComponent { _items:

    Todo[] = []; constructor(private ref: ChangeDetectorRef) { ref.detach(); } @Input() set items(items: Todo[]) { this._items = items; this.ref.detectChanges(); } get items() { return this._items; } }
  33. 84.
  34. 87.

    @Component({ selector: 'todos-cmp', template: `...` changeDetection: ChangeDetectionStrategy.OnPush }) class TodosComponent

    { _items: Todo[] = []; constructor(private ref: ChangeDetectorRef) { ref.detach(); } @Input() set items(items: Todo[]) { this._items = items; this.ref.detectChanges(); } get items() { return this._items; } }
  35. 88.

    @Component({ selector: 'todos-cmp', template: `...` changeDetection: ChangeDetectionStrategy.OnPush }) class TodosComponent

    { _items: Todo[] = []; constructor(private ref: ChangeDetectorRef) { ref.detach(); } @Input() set items(items: Todo[]) { this._items = items; this.ref.detectChanges(); } get items() { return this._items; } }
  36. 89.

    @Component({ selector: 'todos-cmp', template: `...`, changeDetection: ChangeDetectionStrategy.OnPush }) class TodosComponent

    { _items: Todo[] = []; constructor(private ref: ChangeDetectorRef) { ref.detach(); } @Input() set items(items: Todo[]) { this._items = items; this.ref.detectChanges(); } get items() { return this._items; } }
  37. 92.

    twitter.com/mgechev Memoization is a technique used to speed up programs

    by storing the results of function calls and returning the cached result when the same inputs occur again.
  38. 93.

    twitter.com/mgechev Pure Functions ‣ Same result when invoked with same

    arguments ‣ Do not have side-effects Can be optimized with memoization because:
  39. 94.

    const fib = n => { if (n === 1

    || n === 2) return 1; return fib(n - 1) + fib(n - 2); } @Pipe({ name: 'fib', pure: true }) export FibPipe implements PipeTransform { transform(val: number) { return fib(val); } }
  40. 95.

    const fib = n => { if (n === 1

    || n === 2) return 1; return fib(n - 1) + fib(n - 2); } @Pipe({ name: 'fib', pure: true }) export FibPipe implements PipeTransform { transform(val: number) { return fib(val); } }
  41. 96.

    const fib = n => { if (n === 1

    || n === 2) return 1; return fib(n - 1) + fib(n - 2); } @Pipe({ name: 'fib', pure: true }) export FibPipe implements PipeTransform { transform(val: number) { return fib(val); } }