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

AngularJS heart: digest and scopes

AngularJS heart: digest and scopes

Avatar for Borys Semerenko

Borys Semerenko

November 19, 2014
Tweet

More Decks by Borys Semerenko

Other Decks in Programming

Transcript

  1. Scope Scope is an object. Scope is a context where

    the model is stored. Scope is an execution context for expressions.
  2. $rootScope service function Scope() { this.$id = nextUid(); this.$$phase =

    this.$parent = this.$$watchers = this.$$nextSibling = this.$$prevSibling = this.$$childHead = this.$$childTail = null; this.$root = this; this.$$destroyed = false; this.$$listeners = {}; this.$$listenerCount = {}; this.$$isolateBindings = null; } Scope.prototype = { .... }; return $rootScope = new Scope();
  3. Initialization function bootstrap(element, modules, config) { .... var injector =

    createInjector(modules, config.strictDi) ; injector.invoke([ '$rootScope', '$rootElement', '$compile', '$injector', function bootstrapApply (scope, element, compile, injector) { scope. $apply(function() { element. data('$injector', injector); compile(element)(scope) ; }) ; }] ); .... }
  4. function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { .... var compositeLinkFn

    = compileNodes( $compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective, previousCompileContext) ; compile.$$addScopeClass( $compileNodes); return function publicLinkFn(scope, cloneConnectFn, options) { .... $linkNode = $compileNodes; compile. $$addScopeInfo($linkNode, scope) ; .... if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn) ; return $linkNode; }; } Compile
  5. function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, previousCompileContext) { .... for

    (var i = 0; i < nodeList.length; i++) { directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, ignoreDirective); nodeLinkFn = (directives.length) ? applyDirectivesToNode(....) : null; childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !(childNodes = nodeList[i].childNodes) || !childNodes.length) ? null : compileNodes(....); if (nodeLinkFn || childLinkFn) { linkFns.push(i, nodeLinkFn, childLinkFn); linkFnFound = true; nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; } .... } return linkFnFound ? compositeLinkFn : null; <----- !!! } Compile
  6. Compile function collectDirectives (node, directives, attrs, maxPriority, ignoreDirective) { switch

    (nodeType) { case NODE_TYPE_ELEMENT: /* Element */ addDirective(directives, directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective) ; // use the node name: <directive> // iterate over the attributes for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes , j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { addAttrInterpolateDirective(node, directives, value, nName, isNgAttr) ; addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, attrEndName) ; } // use class as directive if (isString(className) && className !== '') { while (match = CLASS_DIRECTIVE_REGEXP. exec(className)) { addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { } } case NODE_TYPE_TEXT: /* Text Node */ addTextInterpolateDirective(directives, node. nodeValue); case NODE_TYPE_COMMENT: /* Comment */ .... } }
  7. Compile function addDirective (tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, endAttrName)

    { if (name === ignoreDirective) return null; if (hasDirectives.hasOwnProperty(name)) { for (var directive, directives = $injector.get(name + Suffix), i = 0, ii = directives. length; i<ii; i++) { .... tDirectives. push(directive) ; } } }
  8. Link function compositeLinkFn (scope, nodeList, $rootElement, parentBoundTranscludeFn) { .... for

    (i = 0, ii = linkFns.length; i < ii;) { nodeLinkFn = linkFns[i++]; if (nodeLinkFn) { if (nodeLinkFn.scope) { childScope = scope.$new(); compile. $$addScopeInfo(jqLite(node), childScope) ; } else { childScope = scope; } .... } } }
  9. Child scope $new: function (isolate, parent) { var child; parent

    = parent || this; if (isolate) { child = new Scope(); child. $root = this.$root; } else { // Only create a child scope class if somebody asks for one, but cache it to allow the VM to optimize lookups. if (!this.$$ChildScope) { this.$$ChildScope = function ChildScope () { this.$$watchers = this.$$nextSibling = this.$$childHead = this.$$childTail = null; this.$$listeners = {}; .... } ; this.$$ChildScope .prototype = this; } child = new this.$$ChildScope() ; } child. $parent = parent; .... return child; }
  10. scope = { $$watchers: null, $$listeners: {}, $id: '009', this:

    $$childScopeClass, $parent: $$childScopeClass, name: 'Borys', location: 'Vinnitsa', __proto___: { $$watchers: null, $$listeners: {}, $id: '007', $parent: Scope, itemsPerPage: 20, getLocation: function() {}, searchMode: false, __proto__: Scope } } Scope inheritance $scope.name = 'Borys'; $scope.location = 'Vinnitsa';
  11. Update scope angular.module( 'todoApp', []).controller( 'TodoController ', ['$scope', function( $scope

    ) { $scope.todos = [ { text: 'learn angular', done: true }, { text: 'build an angular app ', done: false } ]; $scope.addTodo = function() { $scope.todos.push({ text: $scope.todoText, done: false }); $scope.todoText = ''; }; }]);
  12. $watch $watch: function(watchExp, listener, objectEquality) { var get = $parse(watchExp);

    var scope = this, array = scope.$$watchers, watcher = { fn: listener, last: initWatchVal, get: get, exp: watchExp, eq: !!objectEquality }; array.unshift(watcher); return function deregisterWatch () { arrayRemove(array, watcher) ; }; } $scope.$watch('todos', function() { alert('Hey, todos has changed! '); });
  13. $digest $digest: function () { var target = this; beginPhase(

    '$digest'); do { // "while dirty" loop dirty = false; current = target; do { // "traverse the scopes" loop if ((watchers = current. $$watchers)) { length = watchers. length; while (length --) { watch = watchers[length] ; if ((value = watch.get(current)) !== (last = watch.last) && !(watch.eq ? equals(value, last) : ( typeof value === 'number' && typeof last === 'number' && isNaN(value) && isNaN(last)))) { dirty = true; watch.last = watch.eq ? copy(value, null) : value ; watch.fn(value, ((last === initWatchVal) ? value : last), current) ; } } } } while ((current = next)); .... // next = child scope if (dirty && !(ttl--)) { clearPhase() ; throw $rootScopeMinErr(....) ; } } while (dirty) ; clearPhase() ; }
  14. if (!$scope.$$phase) $scope.$apply(); - NO-NO-NO Already in progress Error: $apply

    already in progress Error: $digest already in progress
  15. Performance tip ➔ Angular does not iterate over the properties

    of a scope. It iterates over the watches. ➔ Every watch function is called during every $digest.
  16. $apply $apply: function(expr) { try { beginPhase( '$apply'); return this.$eval(expr);

    } catch (e) { $exceptionHandler(e) ; } finally { clearPhase() ; try { $rootScope.$digest(); } catch (e) { $exceptionHandler(e) ; throw e; } } } $eval: function(expr, locals) { return $parse(expr)(this, locals); } $apply allow to enter the Angular world
  17. Scope $apply, $digest - ? angular.module( 'todoApp', []).controller( 'TodoController ',

    ['$scope', function( $scope ) { $scope.todos = [ { text: 'learn angular', done: true }, { text: 'build an angular app ', done: false } ]; }]); <div ng-controller="TodoController "> <ul> <li ng-repeat="todo in todos"> <input type="checkbox" ng-model="todo.done"> <span class="done-{{todo.done}} ">{{todo.text}}</span> </li> </ul> </div>
  18. Scope $apply, $digest - NO angular.module( 'todoApp', []).controller( 'TodoController ',

    ['$scope', function( $scope ) { setTimeout(function() { $scope.todos = [ { text: 'learn angular', done: true }, { text: 'build an angular app ', done: false } ]; }, 1000); }]); <div ng-controller="TodoController "> <ul> <li ng-repeat="todo in todos"> <input type="checkbox" ng-model="todo.done"> <span class="done-{{todo.done}} ">{{todo.text}}</span> </li> </ul> </div>
  19. app.controller( 'UserController ', ['$scope', function( $scope ) { $scope.title =

    'My team'; $scope.people = [...]; }]); <div> <h1>{{title}}</h1> <ul> <li ng-repeat="person in people "> <span>{{person.name}}</span> <span>{{person.age}} </span> </li> </ul> </div> Static templates
  20. Static templates - bindonce app.controller( 'UserController ', ['$scope', function( $scope

    ) { $scope.title = 'My team'; $scope.people = [...]; }]); <div> <h1 bo-text="title"></h1> <ul> <li bindonce ng -repeat="person in people "> <span bo-text="person.name"></span> <span bo-text="person.age"></span> </li> </ul> </div>
  21. Static templates - one-time binding app.controller( 'UserController ', ['$scope', function(

    $scope ) { $scope.title = 'My team'; $scope.people = [...]; }]); <div> <h1>{{::title}}</h1> <ul> <li ng-repeat="person in ::people "> <span>{{person.name}}</span> <span>{{person.age}} </span> </li> </ul> </div>
  22. $watchCollection $scope.people = [ { name: 'Borys' }, { name:

    'Slava' } ]; $scope.$watchCollection('people', function () { $scope.numberOfPeople = $scope.people.length; });
  23. Watching by value $scope.people = [ { name: 'Borys', tags:

    ['JavaScript', 'Node.js', 'Ajax'] }, { name: 'Slava', tags: ['CSS', 'JavaScript', 'SASS'] } ]; $scope.$watch('people', function () { $scope.numberOfPeople = $scope.people.length; }, true);
  24. angular.module( 'app', []).controller( 'appController', ['$scope', function( $scope ) { $scope.dollars

    = 5; $scope.accounts = ["Tom", "Bobby", "Sally"]; }]).directive( 'awesomeWidget', function() { return { restrict: 'E', template: '<input type="text" ng-model="info" /> ', scope: { info: '=' } }; }); <div ng-controller="appController"> <awesome-widget ng-repeat="account in accounts " info="dollars"></awesome-widget> </div> Binding to a primitive
  25. angular.module( 'app', []).controller( 'appController', ['$scope', function( $scope ) { $scope.customerData

    = { dollars: 5 }; $scope.accounts = ["Tom", "Bobby", "Sally"]; }]).directive( 'awesomeWidget', function() { return { restrict: 'E', template: '<input type="text" ng-model="info.dollars" /> ', scope: { info: '=' } }; }); <div ng-controller="appController"> <awesome-widget ng-repeat="account in accounts " info="customerData"></awesome-widget> </div> Binding to a primitive
  26. Whenever you have ng-model there’s gotta be a dot in

    there somewhere. If you don’t have a dot, you’re doing it wrong. Binding to a primitive