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

Angular - Digest Loop

barnash
February 18, 2014

Angular - Digest Loop

Check out part2 here:
https://speakerdeck.com/barnash/angular-scope-inheritance-digest-loop-part-2
-------------------
Based on the first chapter of "Build Your Own Angular" book:
http://teropa.info/build-your-own-angular/

Building a small version of Scope.

barnash

February 18, 2014
Tweet

More Decks by barnash

Other Decks in Programming

Transcript

  1. We will learn - The magic behind data-binding - An

    inside look into the digest loop - $digest, $watch - Basic methods of scope
  2. The magic Scope (The model) Listen to dom events ->

    update the model: scope.user.name = newValue; <input ng-model=”user.name”>
  3. 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; <input ng-model=”user.name”>
  4. 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; <input ng-model=”user.name”>
  5. Scope.$watch function Scope() { this.$$watchers = []; } Scope.prototype.$watch =

    function(watchFn, listenerFn) { var watcher = { watchFn: watchFn, listenerFn: listenerFn }; this.$$watchers.push(watcher); };
  6. 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(); }) };
  7. 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); } }); };
  8. Getting notified of digest Scope.prototype.$watch = function(watchFn, listenerFn) { var

    watcher = { watchFn: watchFn, listenerFn: listenerFn || function() {} }; this.$$watchers.push(watcher); };
  9. 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(); } });
  10. 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
  11. 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”
  12. 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; };
  13. Keep digesting while dirty (2) Scope.prototype.$digest = function() { var

    dirty; do { dirty = this.$$digestOnce(); } while (dirty); };
  14. 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++; });
  15. 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); };
  16. Deep equal support Scope.prototype.$watch = function(watchFn, listenerFn, valueEq) { var

    watcher = { watchFn: watchFn, listenerFn: listenerFn || function() {}, valueEq: !!valueEq }; this.$$watchers.push(watcher); };
  17. Scope.prototype.$$areEqual = function(newValue, oldValue, valueEq) { if (valueEq) { return

    _.isEqual(newValue, oldValue); } else { return newValue === oldValue; } };
  18. 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; };
  19. 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)); } };
  20. 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)); } };
  21. Performance Watch Watch Watch Watch Watch Watch Watch Watch Watch

    Watch Watch Watch Watch Watch Watch Watch
  22. 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); };
  23. 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; };
  24. $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(); } };
  25. 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); } } };