0

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?

nesteant
  • 1,070
  • 9
  • 16

3 Answers3

1

Minor change yields same results with both prefixes = and @

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 ($timeout) {
return {
    scope: {
        prop: '@'
    },
    template: '<span>{{name}}: {{prop}}</span>',
    controller: function ($scope) {
        $scope.prop = 'controller';
    },
    link: function (scope, element, attr) {
        scope.name = 'isolated';
        $timeout(function(){  });
        $timeout(function(){
            console.log('Still I found attrib value: ',scope.prop);
          
          scope.prop = 'link'; // this will change it
            });
        //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>

But conceptually

@ binding is for passing strings. These strings support {{}} expressions for interpolated values. For example: . The interpolated expression is evaluated against directive's parent scope.

= binding is for two-way model binding. The model in parent scope is linked to the model in the directive's isolated scope. Changes to one model affects the other, and vice versa.

By keeping above concepts in mind, Analysis:

1 - when we define scope with prefix '@' template always gets value from attrib (so link scope.prop affects nothing)

2 - then creates scope and assigns attrib string value to it

But

3 - when second digest cycle will run (on ng-click or ng-model or $timeout) It will change value

4 - see $timeout in above code for understanding (Run It!)

Happy Helping!

Zeeshan Hassan Memon
  • 8,105
  • 4
  • 43
  • 57
  • The problem is not in the digest. As ng-click or timeout will work only with reassigning the property. As you correctly mentioned in the 2 point: scope is created and attribute is assigned to the prop. But conceptually link function is run after that action, thus I should see the new prop, but I still see the attribute value – nesteant Oct 17 '15 at 18:59
0

I believe that parameters in isolate scope are bind AFTER the linking phase. That is why you end up with the value passed via 'prop' attribute, not the one you set in link function.

Could't find in docs it is like I said (https://docs.angularjs.org/guide/compiler), but did a little experiment that have proven it.

Check fiddle - I just initiated 'attribute' property in parent scope and 'dIsolatedWorks' worked similar to 'dIsolated'

<div ng-app="app">
    {{attribute = 'hello world';}}

    <div d-isolated-works prop="attribute"></div>
    <div d-isolated prop="attribute"></div>
</div>

http://jsfiddle.net/ndfqruxf/

The reason why directive 'dIsolatedWorks' was working as you expect is because two way binding was configured on it ('='), but there was no variable on the parent scope named 'attribute'. Therefore, 'dIsolatedWorks' directive initiated the variable.

Misha N.
  • 3,455
  • 1
  • 28
  • 36
  • Working with attribute as a parameter is not an option as with @ operator it is defined as string but not variable. Actually parameters cannot be assigned after the linking as you can work with them freely in the linking block. The problem is that prop value is reassigned somehow – nesteant Oct 17 '15 at 19:05
0

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)

nesteant
  • 1,070
  • 9
  • 16