Search code examples
javascriptangularjsangularjs-directiveisolate-scope

Angularjs attribute binding priority


We have next directive implemented:

angular.module('app', [])
  .directive('dIsolatedWorks', function() {
    return {
      scope: {
        prop: '='
      },
      template: '<span>{{name}}: {{prop}}</span>',
      link: function(scope) {
        scope.name = 'isolated';
        scope.prop = 'link';
      }
    };
  })
  .directive('dIsolated', function() {
    return {
      scope: {
        prop: '@'
      },
      template: '<span>{{name}}: {{prop}}</span>',
      controller: function($scope) {
        $scope.prop = 'controller';
      },
      link: function(scope) {
        scope.name = 'isolated';
        scope.prop = 'link';
      }
    };
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
  <div d-isolated-works prop="attribute"></div>
  <div d-isolated prop="attribute"></div>
</div>

Actually during implementation I was sure that assignment to the scope.prop field will change the variable and it will be displayed as 'link', not 'attribute'. But currently we see that the real value will be isolated: attribute. However it can be simply fixed by changing string assignment to object assignment.

Can you explain such behavior?


Solution

  • So after investigating angularjs code for an answer I have found the correct one - according to angular.js v1.3.20 we have next lines of code in linking function for the @ attributes (line 7698):

          case '@':
                attrs.$observe(attrName, function(value) {
                  isolateBindingContext[scopeName] = value;
                });
                attrs.$$observers[attrName].$$scope = scope;
                if (attrs[attrName]) {
                  // If the attribute has been provided then we trigger an interpolation to ensure
                  // the value is there for use in the link fn
                  isolateBindingContext[scopeName] = $interpolate(attrs[attrName])(scope);
                }
                break;
    

    According to this code

    1. Angularjs registers $observer for attribute (evalAsync thus it will be executed after end of code chain)
    2. Value is correctly propagated from attribute and thus we'll see its value in the linking function
    3. Linking function is propagated with the attribute value.
    4. Linking function changes prop value and finishes its work
    5. Execution is back to the $observer function (because it should execute at least once)
    6. $observer is executed and it changes value back to attribute value

    As a result we can say that usage of string binded params in the directive body is allowed only as a readonly value until it is wrapped into the timeout block (or any other with delayed execution)