Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Faster Angular Applications

Minko Gechev
December 10, 2017

Faster Angular Applications

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, we’ll discuss different techniques which can improve the runtime performance of our application in order to help us achieve rendering with 60fps! In the second part of this talk we’ll explain essential practices that can help us reduce the initial load time of our Angular applications.

Minko Gechev

December 10, 2017
Tweet

More Decks by Minko Gechev

Other Decks in Programming

Transcript

  1. twitter.com/mgechev
    github.com/mgechev
    blog.mgechev.com
    Faster Applications
    Minko Gechev

    View Slide

  2. View Slide

  3. twitter.com/mgechev
    github.com/mgechev

    View Slide

  4. View Slide

  5. Runtime Performance

    View Slide

  6. twitter.com/mgechev
    Simplified
    Business application

    View Slide

  7. View Slide

  8. AppComponent
    EmployeesListComponent

    View Slide

  9. AppComponent
    EmployeesListComponent

    View Slide

  10. AppComponent
    EmployeesListComponent

    View Slide

  11. (keydown)="handleKey($event)">
    *ngFor="let item of data">
    {{ item.label }}
    {{ calculate(item.num) }}
    #

    View Slide

  12. (keydown)="handleKey($event)">
    *ngFor="let item of data">
    {{ item.label }}
    {{ calculate(item.num) }}
    #

    View Slide

  13. (keydown)="handleKey($event)">
    *ngFor="let item of data">
    {{ item.label }}
    {{ calculate(item.num) }}
    #

    View Slide

  14. (keydown)="handleKey($event)">
    *ngFor="let item of data">
    {{ item.label }}
    {{ calculate(item.num) }}
    #

    View Slide

  15. @Component(##...)
    export class EmployeeListComponent {
    @Input() data: EmployeeData[];
    @Output() remove = new EventEmitter();
    @Output() add = new EventEmitter();
    handleKey(event: any) { ##... }
    calculate(num: number) {
    return fibonacci(num);
    }
    }

    View Slide

  16. @Component(##...)
    export class EmployeeListComponent {
    @Input() data: EmployeeData[];
    @Output() remove = new EventEmitter();
    @Output() add = new EventEmitter();
    handleKey(event: any) { ##... }
    calculate(num: number) {
    return fibonacci(num);
    }
    }

    View Slide

  17. AppComponent
    EmployeesListComponent
    data

    View Slide

  18. @Component(##...)
    export class EmployeeListComponent {
    @Input() data: EmployeeData[];
    @Output() remove = new EventEmitter();
    @Output() add = new EventEmitter();
    handleKey(event: any) { ##... }
    calculate(num: number) {
    return fibonacci(num);
    }
    }

    View Slide

  19. const fibonacci = n #=> {
    if (n ##=== 1 #|| n ##=== 2)
    return 1;
    return fibonacci(n - 1)
    + fibonacci(n - 2);
    };

    View Slide

  20. f(n)
    f(n-1) f(n-2)
    f(n-3) f(n-4)
    f(n-2) f(n-3)
    ...

    View Slide

  21. f(n)
    f(n-1) f(n-2)
    f(n-3) f(n-4)
    f(n-2) f(n-3)
    ...

    View Slide

  22. f(n)
    f(n-1) f(n-2)
    f(n-3) f(n-4)
    f(n-2) f(n-3)
    ...

    View Slide

  23. Slowing it down artificially

    View Slide

  24. twitter.com/mgechev
    Application Structure
    • An application component
    • Two list components
    • Slow computation for each entry

    View Slide

  25. twitter.com/mgechev
    Real data…

    View Slide

  26. View Slide

  27. 140 entries

    View Slide

  28. View Slide

  29. Why that slow?

    View Slide

  30. View Slide

  31. @Component(##...)
    export class EmployeeListComponent {
    ##...
    calculate(num: number) {
    console.log('Computing for entry in',
    this.department);
    return fibonacci(num);
    }
    }

    View Slide

  32. View Slide

  33. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  34. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  35. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  36. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  37. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  38. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  39. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  40. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  41. twitter.com/mgechev
    Ideas for optimization?

    View Slide

  42. twitter.com/mgechev
    OnPush
    Change Detection Strategy

    View Slide

  43. twitter.com/mgechev
    With OnPush change detection will be
    triggered when the framework, with reference
    check, determines that any of the inputs of a
    component has changed…

    View Slide

  44. twitter.com/mgechev
    Lets think of EmployeeListComponent as a function, where:
    What does this mean?
    • Inputs are function’s arguments
    • Rendered component is function’s result

    View Slide

  45. Pseudo code (not Angular)
    const f = EmployeeListComponent;
    const data = [e1];
    #// Will trigger CD
    f({ data: data });
    data.push(e2);
    #// Will not trigger CD
    f({ data: data });
    #// Will trigger CD
    f({ data: data.slice() });

    View Slide

  46. Pseudo code (not Angular)
    const f = EmployeeListComponent;
    const data = [e1];
    #// Will trigger CD
    f({ data: data });
    data.push(e2);
    #// Will not trigger CD
    f({ data: data });
    #// Will trigger CD
    f({ data: data.slice() });

    View Slide

  47. Pseudo code (not Angular)
    const f = EmployeeListComponent;
    const data = [e1];
    #// Will trigger CD
    f({ data: data });
    data.push(e2);
    #// Will not trigger CD
    f({ data: data });
    #// Will trigger CD
    f({ data: data.slice() });

    View Slide

  48. Pseudo code (not Angular)
    const f = EmployeeListComponent;
    const data = [e1];
    #// Will trigger CD
    f({ data: data });
    data.push(e2);
    #// Will not trigger CD
    f({ data: data });
    #// Will trigger CD
    f({ data: data.slice() });

    View Slide

  49. twitter.com/mgechev
    Passing new reference
    triggers the change detection

    View Slide

  50. twitter.com/mgechev
    Should we copy the array
    every time?

    View Slide

  51. Why would we do that…?

    View Slide

  52. View Slide

  53. twitter.com/mgechev
    Immutable.js helps:
    • We get a new reference on change
    • We do not copy the entire data structure

    View Slide

  54. @Component({
    template: `
    [data]="list"
    (add)="list = add(list, $event)"
    (remove)="list = remove(list, $event)"
    >#
    `
    })
    export class AppComponent implements OnInit {
    list: List;
    add(list: List, name: string) {
    return list.unshift({ label: name, num: ##... });
    }
    remove(list: List, node: EmployeeData) {
    return list.splice(list.indexOf(node), 1);
    }
    }

    View Slide

  55. @Component({
    template: `
    [data]="list"
    (add)="list = add(list, $event)"
    (remove)="list = remove(list, $event)"
    >#
    `
    })
    export class AppComponent implements OnInit {
    list: List;
    add(list: List, name: string) {
    return list.unshift({ label: name, num: ##... });
    }
    remove(list: List, node: EmployeeData) {
    return list.splice(list.indexOf(node), 1);
    }
    }

    View Slide

  56. @Component({
    template: `
    [data]="list"
    (add)="list = add(list, $event)"
    (remove)="list = remove(list, $event)"
    >#
    `
    })
    export class AppComponent implements OnInit {
    list: List;
    add(list: List, name: string) {
    return list.unshift({ label: name, num: ##... });
    }
    remove(list: List, node: EmployeeData) {
    return list.splice(list.indexOf(node), 1);
    }
    }

    View Slide

  57. twitter.com/mgechev
    Lets see how fast
    it is now!

    View Slide

  58. View Slide

  59. Non Optimized On Push
    Typing Speed

    View Slide

  60. 2x faster but still slow…

    View Slide

  61. View Slide

  62. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  63. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  64. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  65. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  66. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  67. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    Ignored details for simplicity

    View Slide

  68. twitter.com/mgechev
    With OnPush change detection will be
    triggered when the framework, with reference
    check, determines that any of the inputs of a
    component has changed…or when an event
    in the component is triggered

    View Slide

  69. twitter.com/mgechev
    Lets do some refactoring!

    View Slide

  70. AppComponent
    EmployeesListComponent
    NameInputComponent ListComponent

    View Slide

  71. AppComponent
    EmployeesListComponent
    NameInputComponent ListComponent

    View Slide

  72. AppComponent
    EmployeesListComponent
    NameInputComponent ListComponent

    View Slide

  73. View Slide

  74. twitter.com/mgechev
    Sooo much faster!

    View Slide

  75. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    ListComponenet
    (sales)
    ListComponenet
    (r&d)
    Ignored details for simplicity

    View Slide

  76. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    ListComponenet
    (sales)
    ListComponenet
    (r&d)
    Ignored details for simplicity

    View Slide

  77. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    ListComponenet
    (sales)
    ListComponenet
    (r&d)
    Ignored details for simplicity

    View Slide

  78. AppComponent
    EmployeeListComponenet
    (sales)
    EmployeeListComponenet
    (r&d)
    E1 E2 En
    … E1 E2 En

    ListComponenet
    (sales)
    ListComponenet
    (r&d)
    Ignored details for simplicity

    View Slide

  79. Unoptimized Refactored with On Push
    Typing Speed

    View Slide

  80. Adding items

    View Slide

  81. AppComponent
    EmployeeListComponenet
    (sales)
    E1 E2 En

    ListComponenet
    (sales)
    Ignored details for simplicity

    View Slide

  82. AppComponent
    EmployeeListComponenet
    (sales)
    E1 E2 En

    ListComponenet
    (sales)
    Ignored details for simplicity

    View Slide

  83. AppComponent
    EmployeeListComponenet
    (sales)
    E1 E2 En

    ListComponenet
    (sales)
    Ignored details for simplicity

    View Slide

  84. AppComponent
    EmployeeListComponenet
    (sales)
    E1 E2 En

    ListComponenet
    (sales)
    E0
    Ignored details for simplicity

    View Slide

  85. twitter.com/mgechev
    Recomputing everything
    every time we add a new entry

    View Slide

  86. const fibonacci = n #=> {
    if (n ##=== 1 #|| n ##=== 2) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
    };

    View Slide

  87. // Two properties
    // - No side effects
    // - Same result for same arguments
    const fibonacci = n #=> {
    if (n ##=== 1 #|| n ##=== 2) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
    };

    View Slide

  88. twitter.com/mgechev
    Pure Function

    View Slide

  89. twitter.com/mgechev
    Pipes in Angular
    • Pure
    • Impure

    View Slide

  90. twitter.com/mgechev
    Angular executes a pure pipe only when it
    detects a change to the input value. A pure
    change is either a change to a primitive input value
    (String, Number, Boolean, Symbol) or a changed
    object reference (Date, Array, Function, Object).

    View Slide

  91. twitter.com/mgechev
    Angular treats expressions
    with pure pipes as
    referentially transparent

    View Slide

  92. {{ birthday | date }}
    {{ birthday | impureDate }}

    View Slide

  93. {{ birthday | date }}
    {{ birthday | impureDate }}
    i1.ɵunv(_v, 1, 0, _ck(_v, 2, 0,
    i1.ɵnov(_v, 0), _co.birthday));
    i1.ɵunv(_v, 1, 1,
    i1.ɵnov(_v, 3).transform(_co.birthday));

    View Slide

  94. {{ birthday | date }}
    {{ birthday | impureDate }}
    i1.ɵunv(_v, 1, 0, _ck(_v, 2, 0,
    i1.ɵnov(_v, 0), _co.birthday));
    i1.ɵunv(_v, 1, 1,
    i1.ɵnov(_v, 3).transform(_co.birthday));

    View Slide

  95. @Pipe({
    name: 'calculate',
    pure: true
    })
    export class CalculatePipe {
    transform(num: number) {
    return fibonacci(num);
    }
    }

    View Slide

  96. @Component({
    changeDetection:
    ChangeDetectionStrategy.OnPush,
    template: `
    ##...
    {{ item.num | calculate }}
    ##...
    `
    })
    export class ListComponent { ##... }

    View Slide

  97. twitter.com/mgechev
    Lets benchpress it!

    View Slide

  98. View Slide

  99. Refactored with On Push Calculation in a Pure Pipe
    Adding / Removing Entries

    View Slide

  100. twitter.com/mgechev
    Initial rendering…
    with 1000 items

    View Slide

  101. View Slide

  102. View Slide

  103. twitter.com/mgechev
    Lets take a look at
    our data

    View Slide

  104. View Slide

  105. fibonacci(27)

    View Slide

  106. fibonacci(27)
    fibonacci(28)

    View Slide

  107. fibonacci(27)
    fibonacci(28)
    fibonacci(29)

    View Slide

  108. twitter.com/mgechev
    Samples from a small range
    During initial rendering we recompute
    same value multiple times

    View Slide

  109. twitter.com/mgechev
    Solution
    Caching the value once computed

    View Slide

  110. twitter.com/mgechev
    Memoization

    View Slide

  111. twitter.com/mgechev
    Memoization
    possible for pure functions

    View Slide

  112. const memoize = require('lodash.memoize');
    const fibonacci = memoize((num: number): number #=> {
    if (num ##=== 1 #|| num ##=== 2) return 1;
    return fibonacci(num - 1) + fibonacci(num - 2);
    });

    View Slide

  113. const memoize = require('lodash.memoize');
    const fibonacci = memoize((num: number): number #=> {
    if (num ##=== 1 #|| num ##=== 2) return 1;
    return fibonacci(num - 1) + fibonacci(num - 2);
    });

    View Slide

  114. View Slide

  115. View Slide

  116. Pure Pipe Pure Pipe & Memoization
    Initial Rendering

    View Slide

  117. Pure Pipes
    27 | calculate
    27 | calculate
    27 | calculate

    View Slide

  118. Pure Pipes
    27 | calculate
    27 | calculate
    27 | calculate
    fib(27) 196418

    View Slide

  119. Pure Pipes
    27 | calculate
    27 | calculate
    27 | calculate
    fib(27)
    fib(27) 196418
    196418

    View Slide

  120. Pure Pipes
    27 | calculate
    27 | calculate
    27 | calculate fib(27)
    fib(27)
    fib(27) 196418
    196418
    196418

    View Slide

  121. Memoization
    27 | calculate
    27 | calculate
    27 | calculate

    View Slide

  122. 27 | calculate
    27 | calculate
    27 | calculate
    fib(27) 196418
    Memoization

    View Slide

  123. 27 | calculate
    27 | calculate
    27 | calculate
    fib(27) 196418
    cache
    Memoization
    27

    View Slide

  124. 27 | calculate
    27 | calculate
    27 | calculate
    fib(27) 196418
    cache
    fib(27)
    Memoization
    27
    27

    View Slide

  125. 27 | calculate
    27 | calculate
    27 | calculate
    fib(27) 196418
    cache
    fib(27) 196418
    Memoization
    27
    27

    View Slide

  126. 27 | calculate
    27 | calculate
    27 | calculate
    fib(27) 196418
    cache
    fib(27) 196418
    fib(27)
    Memoization
    27
    27
    27

    View Slide

  127. 27 | calculate
    27 | calculate
    27 | calculate
    fib(27) 196418
    cache
    fib(27) 196418
    fib(27) 196418
    Memoization
    27
    27
    27

    View Slide

  128. twitter.com/mgechev
    Pattern…
    • On push performs “memoization”
    • Pure pipes are “memoized”

    View Slide

  129. twitter.com/mgechev
    Pattern…
    for their last input
    • On push performs “memoization”
    • Pure pipes are “memoized”

    View Slide

  130. twitter.com/mgechev
    Lets try to do better!

    View Slide

  131. View Slide

  132. How NgForOf works
    @Directive({selector: '[ngFor][ngForOf]'})
    class NgForOf implements DoCheck, OnChanges {
    ##...
    constructor(private _differs: IterableDiffers) {}
    ngDoCheck(): void {
    const changes = this._differ.diff(this.ngForOf);
    if (changes) this._applyChanges(changes);
    }
    ##...
    }

    View Slide

  133. How NgForOf works
    @Directive({selector: '[ngFor][ngForOf]'})
    class NgForOf implements DoCheck, OnChanges {
    ##...
    constructor(private _differs: IterableDiffers) {}
    ngDoCheck(): void {
    const changes = this._differ.diff(this.ngForOf);
    if (changes) this._applyChanges(changes);
    }
    ##...
    }

    View Slide

  134. class IterableDiffers {
    constructor(factories: IterableDifferFactory[]) {}
    find(iterable: any): IterableDifferFactory {
    const factory =
    this.factories.find(f #=> f.supports(iterable));
    if (factory #!= null) {
    return factory;
    } else {
    throw new Error(
    `Cannot find a differ supporting object`
    );
    }
    }
    #// ##...
    }

    View Slide

  135. interface IterableDifferFactory {
    supports(objects: any): boolean;
    create(trackByFn#?: TrackByFunction):
    IterableDiffer;
    }
    interface IterableDiffer {
    diff(object: NgIterable):
    IterableChanges|null;
    }
    interface TrackByFunction {
    (index: number, item: T): any;
    }

    View Slide

  136. interface IterableDifferFactory {
    supports(objects: any): boolean;
    create(trackByFn#?: TrackByFunction):
    IterableDiffer;
    }
    interface IterableDiffer {
    diff(object: NgIterable):
    IterableChanges|null;
    }
    interface TrackByFunction {
    (index: number, item: T): any;
    }

    View Slide

  137. interface IterableDifferFactory {
    supports(objects: any): boolean;
    create(trackByFn#?: TrackByFunction):
    IterableDiffer;
    }
    interface IterableDiffer {
    diff(object: NgIterable):
    IterableChanges|null;
    }
    interface TrackByFunction {
    (index: number, item: T): any;
    }

    View Slide

  138. interface IterableDifferFactory {
    supports(objects: any): boolean;
    create(trackByFn#?: TrackByFunction):
    IterableDiffer;
    }
    interface IterableDiffer {
    diff(object: NgIterable):
    IterableChanges|null;
    }
    interface TrackByFunction {
    (index: number, item: T): any;
    }

    View Slide

  139. IterableDiffers
    IterableDifferFactory
    IterableDiffer
    TrackByFn
    NgForOf
    Injects
    Uses
    Uses
    Uses
    Sets
    Creates

    View Slide

  140. IterableDiffers
    IterableDifferFactory
    IterableDiffer
    TrackByFn
    NgForOf
    Injects
    Uses
    Uses
    Uses
    Sets
    Creates

    View Slide

  141. IterableDiffers
    IterableDifferFactory
    IterableDiffer
    TrackByFn
    NgForOf
    Injects
    Uses
    Uses
    Uses
    Sets
    Creates

    View Slide

  142. IterableDiffers
    IterableDifferFactory
    IterableDiffer
    TrackByFn
    NgForOf
    Injects
    Uses
    Uses
    Uses
    Sets
    Creates

    View Slide

  143. IterableDiffers
    IterableDifferFactory
    IterableDiffer
    TrackByFn
    NgForOf
    Injects
    Uses
    Uses
    Uses
    Sets
    Creates

    View Slide

  144. How NgForOf works
    @Directive({selector: '[ngFor][ngForOf]'})
    class NgForOf implements DoCheck, OnChanges {
    ##...
    constructor(private _differs: IterableDiffers) {}
    ngDoCheck(): void {
    const changes = this._differ.diff(this.ngForOf);
    if (changes) this._applyChanges(changes);
    }
    ##...
    }

    View Slide

  145. View Slide

  146. const a = [
    { id: 1, name: 'Dan' },
    { id: 2, name: 'Joe' },
    { id: 3, name: 'Josh' }
    ];
    const b = [
    { id: 1, name: 'Dan' },
    { id: 2, name: 'Adam' },
    { id: 4, name: 'Josh' }
    ];
    const trackBy = (_, item) #=> item.id;
    cmp(trackBy(0, a[0]), trackBy(0, b[0]))
    cmp(trackBy(1, a[1]), trackBy(1, b[1]))
    cmp(trackBy(2, a[2]), trackBy(2, b[2]))
    Identical
    Ignored details for simplicity

    View Slide

  147. cmp(trackBy(0, a[0]), trackBy(0, b[0]))
    cmp(trackBy(1, a[1]), trackBy(1, b[1]))
    cmp(trackBy(2, a[2]), trackBy(2, b[2]))
    Identical
    const a = [
    { id: 1, name: 'Dan' },
    { id: 2, name: 'Joe' },
    { id: 3, name: 'Josh' }
    ];
    const b = [
    { id: 1, name: 'Dan' },
    { id: 2, name: 'Adam' },
    { id: 4, name: 'Josh' }
    ];
    const trackBy = (_, item) #=> item.id;
    trackBy(0, a[0]) ##=== trackBy(0, b[0])
    cmp(trackBy(1, a[1]), trackBy(1, b[1]))
    cmp(trackBy(2, a[2]), trackBy(2, b[2]))



    Ignored details for simplicity

    View Slide

  148. cmp(trackBy(0, a[0]), trackBy(0, b[0]))
    cmp(trackBy(1, a[1]), trackBy(1, b[1]))
    cmp(trackBy(2, a[2]), trackBy(2, b[2]))
    Identical
    const trackBy = (_, item) #=> item.id;



    const a = [
    { id: 1, name: 'Dan' },
    { id: 2, name: 'Joe' },
    { id: 3, name: 'Josh' }
    ];
    const b = [
    { id: 1, name: 'Dan' },
    { id: 2, name: 'Adam' },
    { id: 4, name: 'Josh' }
    ];
    trackBy(0, a[0]) ##=== trackBy(0, b[0])
    trackBy(1, a[1]) ##=== trackBy(1, b[1])
    cmp(trackBy(2, a[2]), trackBy(2, b[2]))
    Ignored details for simplicity

    View Slide

  149. cmp(trackBy(0, a[0]), trackBy(0, b[0]))
    cmp(trackBy(1, a[1]), trackBy(1, b[1]))
    cmp(trackBy(2, a[2]), trackBy(2, b[2]))
    Identical
    const trackBy = (_, item) #=> item.id;



    const a = [
    { id: 1, name: 'Dan' },
    { id: 2, name: 'Joe' },
    { id: 3, name: 'Josh' }
    ];
    const b = [
    { id: 1, name: 'Dan' },
    { id: 2, name: 'Adam' },
    { id: 4, name: 'Josh' }
    ];
    trackBy(0, a[0]) ##=== trackBy(0, b[0])
    trackBy(1, a[1]) ##=== trackBy(1, b[1])
    trackBy(2, a[2]) ##=== trackBy(2, b[2])
    cmp(trackBy(2, a[2]), trackBy(2, b[2]))
    Ignored details for simplicity

    View Slide

  150. twitter.com/mgechev
    IterableDiffer checks from
    the outside whether the data
    structure has changed

    View Slide

  151. twitter.com/mgechev
    But the data structure
    knows that best!

    View Slide

  152. export class DifferableList {
    changes = new LinkedList>();
    constructor(private data = List([])) {}
    unshift(data: T) {
    const result = new DifferableList(this.data.unshift(data));
    result.changes.add({ ##... });
    return result;
    }
    ##...
    [Symbol.iterator]() {
    return new DifferableListIterator(this);
    }
    }

    View Slide

  153. export class DifferableList {
    changes = new LinkedList>();
    constructor(private data = List([])) {}
    unshift(data: T) {
    const result = new DifferableList(this.data.unshift(data));
    result.changes.add({ ##... });
    return result;
    }
    ##...
    [Symbol.iterator]() {
    return new DifferableListIterator(this);
    }
    }

    View Slide

  154. export class DifferableList {
    changes = new LinkedList>();
    constructor(private data = List([])) {}
    unshift(data: T) {
    const result = new DifferableList(this.data.unshift(data));
    result.changes.add({ ##... });
    return result;
    }
    ##...
    [Symbol.iterator]() {
    return new DifferableListIterator(this);
    }
    }

    View Slide

  155. export class DifferableList {
    changes = new LinkedList>();
    constructor(private data = List([])) {}
    unshift(data: T) {
    const result = new DifferableList(this.data.unshift(data));
    result.changes.add({ ##... });
    return result;
    }
    ##...
    [Symbol.iterator]() {
    return new DifferableListIterator(this);
    }
    }

    View Slide

  156. export class DifferableList {
    changes = new LinkedList>();
    constructor(private data = List([])) {}
    unshift(data: T) {
    const result = new DifferableList(this.data.unshift(data));
    result.changes.add({ ##... });
    return result;
    }
    ##...
    [Symbol.iterator]() {
    return new DifferableListIterator(this);
    }
    }

    View Slide

  157. twitter.com/mgechev
    Data structure
    optimized for Angular

    View Slide

  158. export class DifferableListDiffer
    implements IterableDiffer, IterableChanges {
    ##...
    diff(collection: NgIterable): DifferableListDiffer | null {
    const changes = this._data.changes;
    this._changes = changes;
    if (changes.size() > 0) {
    this._data.changes = new LinkedList>();
    return this;
    } else {
    return null;
    }
    }
    }

    View Slide

  159. export class DifferableListDiffer
    implements IterableDiffer, IterableChanges {
    ##...
    diff(collection: NgIterable): DifferableListDiffer | null {
    const changes = this._data.changes;
    this._changes = changes;
    if (changes.size() > 0) {
    this._data.changes = new LinkedList>();
    return this;
    } else {
    return null;
    }
    }
    }

    View Slide

  160. twitter.com/mgechev
    Required refactoring

    View Slide

  161. @Component({
    selector: 'sd-employee-list',
    providers: [
    IterableDiffers.extend([
    new DifferableListDifferFactory()
    ])
    ],
    template: `##...`,
    })
    export class EmployeeListComponent { ##... }

    View Slide

  162. twitter.com/mgechev
    Persistent
    data structures
    Inspired by

    View Slide

  163. Default Differ Custom Differ
    Adding / Removing Entries

    View Slide

  164. twitter.com/mgechev
    Recap
    • OnPush change detection
    • When it gets triggered
    • Pure pipes vs Memoization
    • Purity and Referential Transparency
    • Differs and Track by function

    View Slide

  165. twitter.com/mgechev
    Lessons learned
    • No silver bullet
    • Understand your component structure
    • Understand your data
    • Application specific benchmarks

    View Slide

  166. twitter.com/mgechev
    Get inspiration from
    computer science

    View Slide

  167. twitter.com/mgechev
    Links
    mgv.io/ng-cd - Angular’s OnPush Change Detection Strategy
    mgv.io/ng-pure - Pure Pipes and Referential Transparency
    mgv.io/ng-diff - Understanding Angular Differs
    mgv.io/ng-perf-checklist - Angular Performance Checklist
    mgv.io/ng-checklist-video - Angular Performance Checklist (video)

    View Slide

  168. Thank you!
    twitter.com/mgechev
    github.com/mgechev
    blog.mgechev.com

    View Slide