Bringing Immutability to Angular

Bringing Immutability to Angular

Some ideas from the functional paradigm, such as pure functions and immutable data, constantly find broader applications in building user interface.

One of the biggest showdowns in an AngularJS application is caused by the evaluation of hundreds of expressions inside the $digest loop. Using immutable collections can help us reduce the running time of some of them. The unidirectional data flow in Angular 2 allows us to take advantage of the “pure components” for further optimizations in our applications.

In this talk I’m going to make an introduction to how we can take advantage of immutable data structures in our Angular 1 and 2 applications.

82bafb0432ce4ccc9dcc26f94d5fe5bc?s=128

Minko Gechev

May 07, 2015
Tweet

Transcript

  1. Bringing Immutability to Angular Minko Gechev github.com/mgechev twitter.com/mgechev blog.mgechev.com

  2. Minko Gechev github.com/mgechev twitter.com/mgechev { "job": "Freelancer", "hobbies": [ "open

    source", "blogging", "teaching", "sports" ], "communityEvents": [ "SofiaJS", "BeerJS Sofia" ] }
  3. Optimization of the $digest loop Hidden subtopic:

  4. How to optimize collections’ watchers?

  5. $watch vs $watchCollection https://www.flickr.com/photos/118958866@N06/12867045203/

  6. $watch $scope.$watch(expr, fn, deepCheck);

  7. $watch // ... // Most common watches are on primitives,

    in which case we can short // circuit it with === operator, only when === fails do we use .equals if (watch) { if ((value = watch.get(current)) !== (last = watch.last) && !(watch.eq ? equals(value, last) : (typeof value === 'number' && typeof last === 'number' && isNaN(value) && isNaN(last)))) { // ...
  8. // ... // Most common watches are on primitives, in

    which case we can short // circuit it with === operator, only when === fails do we use .equals if (watch) { if ((value = watch.get(current)) !== (last = watch.last) && !(watch.eq ? equals(value, last) : (typeof value === 'number' && typeof last === 'number' && isNaN(value) && isNaN(last)))) { // ... $watch could be slow…
  9. // ... // Most common watches are on primitives, in

    which case we can short // circuit it with === operator, only when === fails do we use .equals if (watch) { if ((value = watch.get(current)) !== (last = watch.last) && !(watch.eq ? equals(value, last) : (typeof value === 'number' && typeof last === 'number' && isNaN(value) && isNaN(last)))) { // ... $watch or much faster…
  10. $watchCollection $scope.$watchCollection(expr, fn);

  11. $watchCollection // ... for (var i = 0; i <

    newLength; i++) { oldItem = oldValue[i]; newItem = newValue[i]; bothNaN = (oldItem !== oldItem) && (newItem !== newItem); if (!bothNaN && (oldItem !== newItem)) { changeDetected++; oldValue[i] = newItem; } } // ...
  12. $watchCollection // ... for (var i = 0; i <

    newLength; i++) { oldItem = oldValue[i]; newItem = newValue[i]; bothNaN = (oldItem !== oldItem) && (newItem !== newItem); if (!bothNaN && (oldItem !== newItem)) { changeDetected++; oldValue[i] = newItem; } } // ... could be slow…
  13. Two optimization options • Use $watch(expr,  fn,  false) • Change

    the reference, i.e. creation of a new collection • Use $watchCollection(expr,  fn)   • Decrease the iterations (possibly to one)
  14. • Use $watch(expr,  fn,  false) • Change the reference, i.e.

    creation of a new collection • Use $watchCollection(expr,  fn)   • Decrease the iterations (possibly to one) Two optimization options
  15. Immutable Data Structures

  16. – Wikipedia “In object-oriented and functional programming, an immutable object

    is an object whose state cannot be modified after it is created.”
  17. None
  18. immutable-demo.js let list = Immutable.List([1, 2, 3]); let changed =

    list.push(4); console.log(changed === list); // false console.log(list); // [ 1, 2, 3 ] console.log(changed); // [ 1, 2, 3, 4 ]
  19. None
  20. Main use cases • Concurrent data access • Avoid side

    effects
  21. How can we take advantage of this in AngularJS?

  22. When immutable collection “changes”, we get a new reference

  23. $scope.$watch(expression, function () { // Handle the change }, false);

  24. None
  25. None
  26. Increase the memory usage? Maintain the persistent data structure

  27. What a persistent data structure is?

  28. Smarter way of implementing immutable collections

  29. Let’s benchpress it!

  30. The benchmark… Main parameters: • Collection Type (immutable or standard)

    • $watch   • $watchCollection   • Collection Size between 5 and 10,000 • Bindings Count between 5 and 100
  31. None
  32. Let’s find the winner! https://www.flickr.com/photos/kiyoungphoto/15657162511/

  33. 5 items Script Running Time Bindings Count

  34. 100 items Script Running Time Bindings Count

  35. 500 items Script Running Time Bindings Count

  36. 10,000 items Script Running Time Bindings Count

  37. Why? Script Running Time Bindings Count

  38. iterates n times over all elements of the results vs

    iterates n times over the results’ references $watchCollection $watch(..,.., false)
  39. None
  40. Caption Garbage Collection Time

  41. 10,000 items Garbage Collection Time Bindings Count

  42. What slows us down? • Garbage collection • Maintaining the

    persistent data structure on change
  43. Lets take one step backwards…

  44. • Use $watch(expr,  fn,  false) • Change the reference, i.e.

    creation of a new collection • Use $watchCollection(expr,  fn)   • Decrease the iterations (possibly to one) Two optimization options
  45. Two optimization options • Use $watch(expr,  fn,  false) • Change

    the reference, i.e. creation of a new collection • Use $watchCollection(expr,  fn)   • Decrease the iterations (possibly to one)
  46. VersionableList function VersionableList(list) { 'use strict’; this._version = 0; defineProperty(this,

    '_data', { enumerable: false, value: list || [] }); } 'push pop shift unshift'.split(' ') .forEach(function (key) { 'use strict'; defineMethod(VersionableList.prototype, key, function () { this._data[key].apply(this._data, arguments); this._updateVersion(); }); });
  47. VersionableList function VersionableList(list) { 'use strict’; this._version = 0; defineProperty(this,

    '_data', { enumerable: false, value: list || [] }); } 'push pop shift unshift'.split(' ') .forEach(function (key) { 'use strict'; defineMethod(VersionableList.prototype, key, function () { this._data[key].apply(this._data, arguments); this._updateVersion(); }); });
  48. VersionableList function VersionableList(list) { 'use strict’; this._version = 0; defineProperty(this,

    '_data', { enumerable: false, value: list || [] }); } 'push pop shift unshift'.split(' ') .forEach(function (key) { 'use strict'; defineMethod(VersionableList.prototype, key, function () { this._data[key].apply(this._data, arguments); this._updateVersion(); }); });
  49. List Keys Object.keys(list); // ['_version']

  50. Usage Demo let list = new VersionableList([1, 2]); console.log(list.valueOf()); //

    [1, 2] console.log(list._version); // 0 list.push(3); console.log(list.valueOf()); // [1, 2, 3] console.log(list._version); // 1
  51. 5 items Script Running Time Bindings Count

  52. 100 items Script Running Time Bindings Count

  53. Script Running Time Bindings Count 10,000 items

  54. Lives @ GitHub (github.com/mgechev/versionable-collections)

  55. Application Specific Benchmarks

  56. What about Angular 2?

  57. In Angular 2 • Unidirectional data flow • One-way data

    binding • No TTL (a single iteration of the $digest) • Less iterations over the watched expressions
  58. None
  59. Angular 2 ❤ Immutability

  60. In Angular 2 we have components

  61. Lets think of the components as functions

  62. Component Input Output {      photoUrl:  “photo.png”,    

     firstName:  “Foo”,      lastName:  “Bar”,      age:  42
 } Perfect World Prof. Foo Bar 42 years old
  63. Component Input Output Prof. Foo Bar 42 years old Reality

    {      //  …
 } {      photoUrl:  “photo.png”,      firstName:  “Foo”,      lastName:  “Bar”,      age:  42
 }
  64. Component Input Output Reality Mutable state {      //

     …
 } {      photoUrl:  “photo.png”,      firstName:  “Foo”,      lastName:  “Bar”,      age:  42
 } Prof. Foo Bar 42 years old
  65. app todos todo todo user

  66. app todos todo todo user {      photoUrl:  “photo.png”,

         firstName:  “Foo”,      lastName:  “Bar”,      age:  42,      updated:  1429260707251,      todos:  [{          title:  “Save  the  Universe”,          updated:  1429260907251,          completed:  true      },  {          title:  “Write  tests”,          updated:  1429260917251,          completed:  false      }]
 }
  67. app todos todo todo user {      photoUrl:  “photo.png”,

         firstName:  “Foo”,      lastName:  “Bar”,      age:  42,      updated:  1429260707251,      todos:  [{          title:  “Save  the  Universe”,          updated:  1429260907251,          completed:  true      },  {          title:  “Write  tests”,          updated:  1429260917251,          completed:  false      }]
 }
  68. app todos todo todo user {      photoUrl:  “photo.png”,

         firstName:  “Foo”,      lastName:  “Bar”,      age:  42,      updated:  1429260707251,      todos:  [{          title:  “Save  the  Universe”,          updated:  1429260907251,          completed:  true      },  {          title:  “Write  tests”,          updated:  1429260917251,          completed:  false      }]
 }
  69. app todos todo todo user user.todos user.todos[0] user.todos[1] user user

  70. app todos todo todo user Prof. Foo Bar 42 years

    old 1 todo pending [x] Save the Universe [x] Write tests
  71. app todos todo todo user user.todos = user.todos.filter(t => !t.completed

    });
  72. app todos todo todo user user.todos = user.todos.filter(t => !t.completed

    });
  73. app todos todo todo user user.todos = user.todos.filter(t => !t.completed

    }); Change user.todos • todos will get wrong items • Angular needs to perform change detection over the whole tree
  74. app todos todo todo user user.todos = user.todos.filter(t => !t.completed

    }); Change user.todos • todos will get wrong items • Angular needs to perform change detection over the whole tree
  75. app todos todo todo user user.todos = user.todos.filter(t => !t.completed

    }); Change user.todos • todos will get wrong items • Angular needs to perform change detection over the whole tree
  76. Component Input Output Prof. Foo Bar 42 years old Reality

    Mutable state {      //  …
 } {      photoUrl:  “photo.png”,      firstName:  “Foo”,      lastName:  “Bar”,      age:  42
 }
  77. Immutable Data & Object.freeze

  78. Why Object.freeze?

  79. immutable data structure mutable items

  80. None
  81. None
  82. None
  83. None
  84. None
  85. None
  86. None
  87. Change Detection Reinvented Change Detection in Angular 2

  88. References • Boost the Performance of an AngularJS App Using

    Immutable Data - Part 1 • Boost the Performance of an AngularJS App Using Immutable Data - Part 2 • Even Faster AngularJS Data Structures • angular-immutable • Persistent Data Structures • Benchmarks • Versionable collections • Change Detection Reinvented • Change Detection in Angular 2 • Immutable.js
  89. Thank you! github.com/mgechev twitter.com/mgechev blog.mgechev.com