Slide 1

Slide 1 text

Angular.js Into the digest loop Iftach Bar

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

We will learn - The magic behind data-binding - An inside look into the digest loop - $digest, $watch - Basic methods of scope

Slide 4

Slide 4 text

Motivation HTML does not support data-binding

Slide 5

Slide 5 text

Motivation HTML does not support data-binding But Angular.js does it!

Slide 6

Slide 6 text

Motivation HTML does not support data-binding But Angular.js does it! Linux!

Slide 7

Slide 7 text

The magic Scope (The model) scope.user.name = “Iftach”

Slide 8

Slide 8 text

The magic Scope (The model) Listen to dom events -> update the model: scope.user.name = newValue;

Slide 9

Slide 9 text

The magic Scope (The model) Listen to dom events -> update the model: scope.user.name = newValue; Let me know when this expression changes -> update the dom: dom.value = scope.user.name;

Slide 10

Slide 10 text

There is no spoon Scope (The model) Listen to dom events -> update the model: scope.user.name = newValue; Let me know when this expression changes -> update the dom: dom.value = scope.user.name;

Slide 11

Slide 11 text

Let’s dive in

Slide 12

Slide 12 text

Scope function Scope() { }

Slide 13

Slide 13 text

Scope.$watch function Scope() { this.$$watchers = []; } Scope.prototype.$watch = function(watchFn, listenerFn) { var watcher = { watchFn: watchFn, listenerFn: listenerFn }; this.$$watchers.push(watcher); };

Slide 14

Slide 14 text

Scope.$digest Scope.prototype.$watch = function(watchFn, listenerFn) { var watcher = { watchFn: watchFn, listenerFn: listenerFn }; this.$$watchers.push(watcher); }; Scope.prototype.$digest = function() { _.forEach(this.$$watchers, function(watch) { watch. listenerFn(); }) };

Slide 15

Slide 15 text

Checking for dirty values Scope.prototype.$digest = function() { var self = this; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch. last; if (newValue !== oldValue) { watch. last = newValue; watch. listenerFn(newValue, oldValue, self); } }); };

Slide 16

Slide 16 text

Getting notified of digest

Slide 17

Slide 17 text

Getting notified of digest Scope.prototype.$watch = function(watchFn, listenerFn) { var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() {} }; this.$$watchers.push(watcher); };

Slide 18

Slide 18 text

Problem var scope = new Scope(); scope.name = “iftach”; scope.$watch(function(scope) { return scope.nameUpper; }, function (newValue) { if (newValue) { scope. initial = newValue.substring(0, 1) + '.'; } }); scope.$watch(function(scope) { return scope.name; }, function(newValue) { if (newValue) { scope. nameUpper = newValue.toUpperCase(); } });

Slide 19

Slide 19 text

Problem name = “iftach” nameUpper = undefined initial = undefined name = “iftach” nameUpper = undefined initial = undefined name = “iftach” nameUpper = “IFTACH” initial = undefined Running digest After watch of nameUpper After watch of name

Slide 20

Slide 20 text

Problem name = “iftach” nameUpper = undefined initial = undefined name = “iftach” nameUpper = undefined initial = undefined name = “iftach” nameUpper = “IFTACH” initial = undefined But we expected: name = “iftach” nameUpper = “IFTACH” initial = “I”

Slide 21

Slide 21 text

Keep digesting while dirty (1) Scope.prototype.$$digestOnce = function() { var self = this; var dirty = false; _.forEach(this.$$watchers, function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (newValue !== oldValue) { watch.last = newValue; watch.listenerFn(newValue, oldValue, self); dirty = true; } }); return dirty; };

Slide 22

Slide 22 text

Keep digesting while dirty (2) Scope.prototype.$digest = function() { var dirty; do { dirty = this.$$digestOnce(); } while (dirty); };

Slide 23

Slide 23 text

Problem var scope = new Scope(); scope.c1 = 0; scope.c2 = 0; scope.$watch(function(scope) { return scope.c1; }, function() { scope.c2++; }); scope.$watch(function(scope) { return scope.c2; }, function() { scope.c1++; });

Slide 24

Slide 24 text

Unstable digest Scope.prototype.$digest = function() { var ttl = 10; var dirty; do { dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { throw "10 digest iterations reached"; } } while (dirty); };

Slide 25

Slide 25 text

Deep equal support Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) { var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() {}, valueEq: !!valueEq }; this.$$watchers.push(watcher); };

Slide 26

Slide 26 text

Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return _.isEqual(newValue, oldValue); } else { return newValue === oldValue; } };

Slide 27

Slide 27 text

Scope.prototype.$$digestOnce = function() { var self = this; var dirty = false; _.forEach(this.$$watchers, function(watch) { var newValue = watch. watchFn(self); var oldValue = watch. last; if (!self.$$areEqual(newValue, oldValue, watch. valueEq)) { watch. last = (watch.valueEq ? _.cloneDeep(newValue) : newValue; watch. listenerFn(newValue, oldValue, self); dirty = true; } }); return dirty; };

Slide 28

Slide 28 text

NaN !== NaN Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return _.isEqual(newValue, oldValue); } else { return newValue === oldValue || (typeof newValue === 'number' && typeof oldValue === 'number' && isNaN(newValue) && isNaN(oldValue)); } };

Slide 29

Slide 29 text

NaN !== NaN Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return _.isEqual(newValue, oldValue); } else { return newValue === oldValue || (typeof newValue === 'number' && typeof oldValue === 'number' && isNaN(newValue) && isNaN(oldValue)); } };

Slide 30

Slide 30 text

Performance scope.$watch(...) scope.$watch(...) scope.$watch(...) scope.$watch(...) scope.$watch(...) scope.$digest()

Slide 31

Slide 31 text

Performance Watch 1 Watch 2 Watch 3 Watch 4 Watch 5

Slide 32

Slide 32 text

Performance Watch 1 Watch 2 Watch 3 Watch 4 Watch 5

Slide 33

Slide 33 text

Performance Watch 1 Watch 2 Watch 3 Watch 4 Watch 5

Slide 34

Slide 34 text

Performance Watch 1 Watch 2 Watch 3 Watch 4 Watch 5

Slide 35

Slide 35 text

Performance Watch 1 Watch 2 Watch 3 Watch 4 Watch 5

Slide 36

Slide 36 text

Performance Watch 1 Watch 2 Watch 3 Watch 4 Watch 5

Slide 37

Slide 37 text

Performance Watch Watch Watch Watch Watch Watch Watch Watch Watch Watch Watch Watch Watch Watch Watch Watch

Slide 38

Slide 38 text

Improve performance a bit function Scope() { this.$$watchers = []; this.$$lastDirtyWatch = null; } Scope.prototype.$digest = function() { var ttl = 10; var dirty; this.$$lastDirtyWatch = null; do { dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { throw "10 digest iterations reached"; } } while (dirty); };

Slide 39

Slide 39 text

Scope.prototype.$$digestOnce = function() { var self = this; var dirty = false; this.$$watchers.every(function(watch) { var newValue = watch.watchFn(self); var oldValue = watch.last; if (!self.$$areEqual(newValue, oldValue, watch.valueEq)) { watch.last = (watch.valueEq ? _.cloneDeep(newValue) : newValue; watch.listenerFn(newValue, oldValue, self); dirty = true; self.$$lastDirtyWatch = watch; } else if (self.$$lastDirtyWatch === watch) { return false; } return true; }); return dirty; };

Slide 40

Slide 40 text

$eval vs $apply Scope.prototype.$eval = function(expr, locals) { return expr(this, locals); }; Scope.prototype.$apply = function(expr) { try { return this.$eval(expr); } finally { this.$digest(); } };

Slide 41

Slide 41 text

Destroying a watch Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) { var self = this; var watcher = { watchFn: watchFn, listenerFn: listenerFn || function() {}, valueEq: !!valueEq }; self.$$watchers.push(watcher); return function() { var index = self.$$watchers.indexOf(watcher); if (index >= 0) { self.$$watchers.splice(index, 1); } } };

Slide 42

Slide 42 text

Questions

Slide 43

Slide 43 text

Stuff we didn’t discuss $evalAsync Scope phases $$postDigest Handing Exceptions http://teropa.info/build-your-own-angular

Slide 44

Slide 44 text

Me? Iftach Bar - Working on Deflect.io - Freelancing http://www.deflect.io

Slide 45

Slide 45 text

Thank you