Faster Angular Applications

82bafb0432ce4ccc9dcc26f94d5fe5bc?s=47 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.

82bafb0432ce4ccc9dcc26f94d5fe5bc?s=128

Minko Gechev

December 10, 2017
Tweet

Transcript

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

  2. None
  3. twitter.com/mgechev github.com/mgechev

  4. None
  5. Runtime Performance

  6. twitter.com/mgechev Simplified Business application

  7. None
  8. AppComponent EmployeesListComponent

  9. AppComponent EmployeesListComponent

  10. AppComponent EmployeesListComponent

  11. <input [(ngModel)]="label" (keydown)="handleKey($event)"> <mat-list-item *ngFor="let item of data"> {{ item.label

    }} {{ calculate(item.num) }} #</mat-list-item>
  12. <input [(ngModel)]="label" (keydown)="handleKey($event)"> <mat-list-item *ngFor="let item of data"> {{ item.label

    }} {{ calculate(item.num) }} #</mat-list-item>
  13. <input [(ngModel)]="label" (keydown)="handleKey($event)"> <mat-list-item *ngFor="let item of data"> {{ item.label

    }} {{ calculate(item.num) }} #</mat-list-item>
  14. <input [(ngModel)]="label" (keydown)="handleKey($event)"> <mat-list-item *ngFor="let item of data"> {{ item.label

    }} {{ calculate(item.num) }} #</mat-list-item>
  15. @Component(##...) export class EmployeeListComponent { @Input() data: EmployeeData[]; @Output() remove

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

    = new EventEmitter<EmployeeData>(); @Output() add = new EventEmitter<string>(); handleKey(event: any) { ##... } calculate(num: number) { return fibonacci(num); } }
  17. AppComponent EmployeesListComponent data

  18. @Component(##...) export class EmployeeListComponent { @Input() data: EmployeeData[]; @Output() remove

    = new EventEmitter<EmployeeData>(); @Output() add = new EventEmitter<string>(); handleKey(event: any) { ##... } calculate(num: number) { return fibonacci(num); } }
  19. const fibonacci = n #=> { if (n ##=== 1

    #|| n ##=== 2) return 1; return fibonacci(n - 1) + fibonacci(n - 2); };
  20. f(n) f(n-1) f(n-2) f(n-3) f(n-4) f(n-2) f(n-3) ...

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

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

  23. Slowing it down artificially

  24. twitter.com/mgechev Application Structure • An application component • Two list

    components • Slow computation for each entry
  25. twitter.com/mgechev Real data…

  26. None
  27. 140 entries

  28. None
  29. Why that slow?

  30. None
  31. @Component(##...) export class EmployeeListComponent { ##... calculate(num: number) { console.log('Computing

    for entry in', this.department); return fibonacci(num); } }
  32. None
  33. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … Ignored details for simplicity
  34. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … Ignored details for simplicity
  35. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … Ignored details for simplicity
  36. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … Ignored details for simplicity
  37. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … Ignored details for simplicity
  38. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … Ignored details for simplicity
  39. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … Ignored details for simplicity
  40. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … Ignored details for simplicity
  41. twitter.com/mgechev Ideas for optimization?

  42. twitter.com/mgechev OnPush Change Detection Strategy

  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…
  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
  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() });
  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() });
  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() });
  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() });
  49. twitter.com/mgechev Passing new reference triggers the change detection

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

  51. Why would we do that…?

  52. None
  53. twitter.com/mgechev Immutable.js helps: • We get a new reference on

    change • We do not copy the entire data structure
  54. @Component({ template: ` <sd-employee-list [data]="list" (add)="list = add(list, $event)" (remove)="list

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

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

    = remove(list, $event)" >#</sd-employee-list> ` }) export class AppComponent implements OnInit { list: List<EmployeeData>; add(list: List<EmployeeData>, name: string) { return list.unshift({ label: name, num: ##... }); } remove(list: List<EmployeeData>, node: EmployeeData) { return list.splice(list.indexOf(node), 1); } }
  57. twitter.com/mgechev Lets see how fast it is now!

  58. None
  59. Non Optimized On Push Typing Speed

  60. 2x faster but still slow…

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

    E2 En … Ignored details for simplicity
  63. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … Ignored details for simplicity
  64. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … Ignored details for simplicity
  65. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … Ignored details for simplicity
  66. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … Ignored details for simplicity
  67. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … Ignored details for simplicity
  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
  69. twitter.com/mgechev Lets do some refactoring!

  70. AppComponent EmployeesListComponent NameInputComponent ListComponent

  71. AppComponent EmployeesListComponent NameInputComponent ListComponent

  72. AppComponent EmployeesListComponent NameInputComponent ListComponent

  73. None
  74. twitter.com/mgechev Sooo much faster!

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

    E2 En … ListComponenet (sales) ListComponenet (r&d) Ignored details for simplicity
  76. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … ListComponenet (sales) ListComponenet (r&d) Ignored details for simplicity
  77. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … ListComponenet (sales) ListComponenet (r&d) Ignored details for simplicity
  78. AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1

    E2 En … ListComponenet (sales) ListComponenet (r&d) Ignored details for simplicity
  79. Unoptimized Refactored with On Push Typing Speed

  80. Adding items

  81. AppComponent EmployeeListComponenet (sales) E1 E2 En … ListComponenet (sales) Ignored

    details for simplicity
  82. AppComponent EmployeeListComponenet (sales) E1 E2 En … ListComponenet (sales) Ignored

    details for simplicity
  83. AppComponent EmployeeListComponenet (sales) E1 E2 En … ListComponenet (sales) Ignored

    details for simplicity
  84. AppComponent EmployeeListComponenet (sales) E1 E2 En … ListComponenet (sales) E0

    Ignored details for simplicity
  85. twitter.com/mgechev Recomputing everything every time we add a new entry

  86. const fibonacci = n #=> { if (n ##=== 1

    #|| n ##=== 2) return 1; return fibonacci(n - 1) + fibonacci(n - 2); };
  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); };
  88. twitter.com/mgechev Pure Function

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

  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).
  91. twitter.com/mgechev Angular treats expressions with pure pipes as referentially transparent

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

  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));
  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));
  95. @Pipe({ name: 'calculate', pure: true }) export class CalculatePipe {

    transform(num: number) { return fibonacci(num); } }
  96. @Component({ changeDetection: ChangeDetectionStrategy.OnPush, template: ` ##... {{ item.num | calculate

    }} ##... ` }) export class ListComponent { ##... }
  97. twitter.com/mgechev Lets benchpress it!

  98. None
  99. Refactored with On Push Calculation in a Pure Pipe Adding

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

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

  104. None
  105. fibonacci(27)

  106. fibonacci(27) fibonacci(28)

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

  108. twitter.com/mgechev Samples from a small range During initial rendering we

    recompute same value multiple times
  109. twitter.com/mgechev Solution Caching the value once computed

  110. twitter.com/mgechev Memoization

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

  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); });
  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); });
  114. None
  115. None
  116. Pure Pipe Pure Pipe & Memoization Initial Rendering

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

    calculate
  118. Pure Pipes 27 | calculate 27 | calculate 27 |

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

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

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

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

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

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

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

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

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

    196418 cache fib(27) 196418 fib(27) 196418 Memoization 27 27 27
  128. twitter.com/mgechev Pattern… • On push performs “memoization” • Pure pipes

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

    “memoization” • Pure pipes are “memoized”
  130. twitter.com/mgechev Lets try to do better!

  131. None
  132. How NgForOf works @Directive({selector: '[ngFor][ngForOf]'}) class NgForOf<T> implements DoCheck, OnChanges

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

    { ##... constructor(private _differs: IterableDiffers) {} ngDoCheck(): void { const changes = this._differ.diff(this.ngForOf); if (changes) this._applyChanges(changes); } ##... }
  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` ); } } #// ##... }
  135. interface IterableDifferFactory { supports(objects: any): boolean; create<V>(trackByFn#?: TrackByFunction<V>): IterableDiffer<V>; }

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

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

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

    interface IterableDiffer<V> { diff(object: NgIterable<V>): IterableChanges<V>|null; } interface TrackByFunction<T> { (index: number, item: T): any; }
  139. IterableDiffers IterableDifferFactory IterableDiffer TrackByFn NgForOf Injects Uses Uses Uses Sets

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

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

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

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

    Creates
  144. How NgForOf works @Directive({selector: '[ngFor][ngForOf]'}) class NgForOf<T> implements DoCheck, OnChanges

    { ##... constructor(private _differs: IterableDiffers) {} ngDoCheck(): void { const changes = this._differ.diff(this.ngForOf); if (changes) this._applyChanges(changes); } ##... }
  145. None
  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
  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
  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
  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
  150. twitter.com/mgechev IterableDiffer checks from the outside whether the data structure

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

  152. export class DifferableList<T> { changes = new LinkedList<IterableChangeRecord<T#>>(); constructor(private data

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

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

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

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

    = List<T>([])) {} unshift(data: T) { const result = new DifferableList<T>(this.data.unshift(data)); result.changes.add({ ##... }); return result; } ##... [Symbol.iterator]() { return new DifferableListIterator<T>(this); } }
  157. twitter.com/mgechev Data structure optimized for Angular

  158. export class DifferableListDiffer<V> implements IterableDiffer<V>, IterableChanges<V> { ##... diff(collection: NgIterable<V>):

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

    DifferableListDiffer<V> | null { const changes = this._data.changes; this._changes = changes; if (changes.size() > 0) { this._data.changes = new LinkedList<IterableChangeRecord<V#>>(); return this; } else { return null; } } }
  160. twitter.com/mgechev Required refactoring

  161. @Component({ selector: 'sd-employee-list', providers: [ IterableDiffers.extend([ new DifferableListDifferFactory() ]) ],

    template: `##...`, }) export class EmployeeListComponent { ##... }
  162. twitter.com/mgechev Persistent data structures Inspired by

  163. Default Differ Custom Differ Adding / Removing Entries

  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
  165. twitter.com/mgechev Lessons learned • No silver bullet • Understand your

    component structure • Understand your data • Application specific benchmarks
  166. twitter.com/mgechev Get inspiration from computer science

  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)
  168. Thank you! twitter.com/mgechev github.com/mgechev blog.mgechev.com