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

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