Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

Minko Gechev github.com/mgechev twitter.com/mgechev { "job": "Freelancer", "hobbies": [ "open source", "blogging", "teaching", "sports" ], "communityEvents": [ "SofiaJS", "BeerJS Sofia" ] }

Slide 3

Slide 3 text

Optimization of the $digest loop Hidden subtopic:

Slide 4

Slide 4 text

How to optimize collections’ watchers?

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

$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)))) { // ...

Slide 8

Slide 8 text

// ... // 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…

Slide 9

Slide 9 text

// ... // 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…

Slide 10

Slide 10 text

$watchCollection $scope.$watchCollection(expr, fn);

Slide 11

Slide 11 text

$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; } } // ...

Slide 12

Slide 12 text

$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…

Slide 13

Slide 13 text

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)

Slide 14

Slide 14 text

• 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

Slide 15

Slide 15 text

Immutable Data Structures

Slide 16

Slide 16 text

– Wikipedia “In object-oriented and functional programming, an immutable object is an object whose state cannot be modified after it is created.”

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

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 ]

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Main use cases • Concurrent data access • Avoid side effects

Slide 21

Slide 21 text

How can we take advantage of this in AngularJS?

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

Increase the memory usage? Maintain the persistent data structure

Slide 27

Slide 27 text

What a persistent data structure is?

Slide 28

Slide 28 text

Smarter way of implementing immutable collections

Slide 29

Slide 29 text

Let’s benchpress it!

Slide 30

Slide 30 text

The benchmark… Main parameters: • Collection Type (immutable or standard) • $watch   • $watchCollection   • Collection Size between 5 and 10,000 • Bindings Count between 5 and 100

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

Let’s find the winner! https://www.flickr.com/photos/kiyoungphoto/15657162511/

Slide 33

Slide 33 text

5 items Script Running Time Bindings Count

Slide 34

Slide 34 text

100 items Script Running Time Bindings Count

Slide 35

Slide 35 text

500 items Script Running Time Bindings Count

Slide 36

Slide 36 text

10,000 items Script Running Time Bindings Count

Slide 37

Slide 37 text

Why? Script Running Time Bindings Count

Slide 38

Slide 38 text

iterates n times over all elements of the results vs iterates n times over the results’ references $watchCollection $watch(..,.., false)

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

Caption Garbage Collection Time

Slide 41

Slide 41 text

10,000 items Garbage Collection Time Bindings Count

Slide 42

Slide 42 text

What slows us down? • Garbage collection • Maintaining the persistent data structure on change

Slide 43

Slide 43 text

Lets take one step backwards…

Slide 44

Slide 44 text

• 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

Slide 45

Slide 45 text

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)

Slide 46

Slide 46 text

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(); }); });

Slide 47

Slide 47 text

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(); }); });

Slide 48

Slide 48 text

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(); }); });

Slide 49

Slide 49 text

List Keys Object.keys(list); // ['_version']

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

5 items Script Running Time Bindings Count

Slide 52

Slide 52 text

100 items Script Running Time Bindings Count

Slide 53

Slide 53 text

Script Running Time Bindings Count 10,000 items

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

Application Specific Benchmarks

Slide 56

Slide 56 text

What about Angular 2?

Slide 57

Slide 57 text

In Angular 2 • Unidirectional data flow • One-way data binding • No TTL (a single iteration of the $digest) • Less iterations over the watched expressions

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 text

Angular 2 ❤ Immutability

Slide 60

Slide 60 text

In Angular 2 we have components

Slide 61

Slide 61 text

Lets think of the components as functions

Slide 62

Slide 62 text

Component Input Output {      photoUrl:  “photo.png”,      firstName:  “Foo”,      lastName:  “Bar”,      age:  42
 } Perfect World Prof. Foo Bar 42 years old

Slide 63

Slide 63 text

Component Input Output Prof. Foo Bar 42 years old Reality {      //  …
 } {      photoUrl:  “photo.png”,      firstName:  “Foo”,      lastName:  “Bar”,      age:  42
 }

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

app todos todo todo user

Slide 66

Slide 66 text

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      }]
 }

Slide 67

Slide 67 text

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      }]
 }

Slide 68

Slide 68 text

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      }]
 }

Slide 69

Slide 69 text

app todos todo todo user user.todos user.todos[0] user.todos[1] user user

Slide 70

Slide 70 text

app todos todo todo user Prof. Foo Bar 42 years old 1 todo pending [x] Save the Universe [x] Write tests

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

Immutable Data & Object.freeze

Slide 78

Slide 78 text

Why Object.freeze?

Slide 79

Slide 79 text

immutable data structure mutable items

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

No content

Slide 83

Slide 83 text

No content

Slide 84

Slide 84 text

No content

Slide 85

Slide 85 text

No content

Slide 86

Slide 86 text

No content

Slide 87

Slide 87 text

Change Detection Reinvented Change Detection in Angular 2

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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