Angular directive template binding not updated

I have a directive created here http://jsfiddle.net/screenm0nkey/8Cw4z/3 that has two bindings to the same scope property, but for some reason the binding to the property of the directive property doesn't when the model changes (after entering the input).

<test> <h3>Inner {{count}}</h3> <input type="text" ng-model="count"> </test> var App = angular.module('App', []); App.directive('test', function() { return { restrict: 'E', replace: true, transclude: true, template: "<h1>Outer{{count}} <div ng-transclude></div></h1>", controller: function ($scope) { $scope.count = 1; } }; }); 

But if I move the input position in the markup, it works and both bindings are updated.

 <input type="text" ng-model="count"> <test> <h3>Inner {{count}}</h3> </test> 

http://jsfiddle.net/screenm0nkey/dCvZk/3

Can anyone explain why the input position containing the binding will affect the bindings. I assumed that during the digest cycle, the binding observers will be updated regardless of the markup position.

Many thanks

+7
angularjs angularjs-directive
source share
7 answers

For me this seems like a pure problem. Let's look at the markup that is generated by both:

Does not work:

 <body ng-app="App" class="ng-scope"> <h1 class="ng-binding">Outer1 <div ng-transclude=""> <h3 class="ng-scope ng-binding">Inner 1</h3> <input type="text" ng-model="count" class="ng-scope ng-pristine ng-valid"> </div> </h1> </body> 

IN:

 <body ng-app="App" class="ng-scope"> <input type="text" ng-model="count" class="ng-valid ng-dirty"> <h1 class="ng-binding">Outer <div ng-transclude=""> <h3 class="ng-scope ng-binding">Inner </h3> </div> </h1> </body> 

The ng-scope class is a useful marker where Angular declares a new scope.

From the markup, you can see that in the working example, both count properties are enclosed in scope , which are attached to the body . So, in this case, the directive scope is a child of the body scope (and therefore has access to it).

However, in an example that does not work, the Outer1 property is out of scope of the input .

Angular Area Documentation covers this issue. Regions are arranged in a hierarchy with child regions that have access to the parent regions (but not vice versa):

An application may have several areas, since some directives create new child areas (see the directive documentation to see which directives create new areas of application). When new areas are created, they are added as children from their parent area. This creates a tree structure that is parallel to the DOM where they are attached.

angular scope heirarchy

+2
source share

The story of a long story - as others have said, is a matter of scale. Using the "ng-transclude" directive creates a new scope. When a new region is created, values ​​from the old region will be available in the new region (therefore, the first replacement), but after that only those objects that are divided between the old / new region will be updated. This is why using the object will work, but using the value will not.

In your case, placing the input field inside ng-transclude results in that it only edits the value in this area, and not the value in the parent area (from which the counter is drawn for the test directive).

By the way, this can be a problem with repeaters (ng-repeat), as well as with other directives. It’s best to use a tool like Batarang "to find such problems. This allows you to look at what is in each area and determine why the" correct "data is not displayed on the screen. Hope this helps to explain further!

+2
source share

Here work around

Change $scope.count to

 $scope.helper = { count: 1 } 

and reorganize the rest.

Check out this video for an explanation.

+1
source share

The order matters because of the differences between creating a property in scope and the actual use of the object associated with the scope (especially when transclude creates a new child scopr). The best practice is to use an object in scope and bind properties to that object when problems with the scope can come into play with directives and translations.

If you change your code to this, it will work as you expected, and the order does not matter. Notice that I create an area object and put the score as a property on this object.

 <test> <h3>Inner {{data.count}}</h3> <input type="text" ng-model="data.count"/> </test> 

 var App = angular.module('App', []); App.directive('test', function() { return { restrict: 'E', replace: true, transclude: true, template: "<h1>Outer{{data.count}} <div ng-transclude></div></h1>", controller: function ($scope) { $scope.data = {}; $scope.data.count = 1; } }; }); 

This is a great tutorial on this. Supports to EggHead. https://egghead.io/lessons/angularjs-the-dot

+1
source share

Add ng-change to input , it should work. The problem is that the controller in the directive is not aware of the change in count .

Js

 var App = angular.module('App', []); App.directive('test', function () { return { restrict: 'E', replace: true, transclude: true, template: "<h1>Outer {{this.count}} <div ng-transclude></div></h1>", controller: function ($scope) { $scope.count = 1; $scope.onChange = function(count){ $scope.count = count; } } }; }); 

HTML

 <test> <h3>Inner {{count}}</h3> <input type="text" ng-model="count" ng-change="onChange(count)"> </test> 

Fiddle Demo

+1
source share

This is problem.

$scope.count = 1; adds the count property to the area where <test> is located. Let me call it the parent area.

ng-transclude creates a new region, allowing it to be called its child region. When <h3>Inner {{count}}</h3> is evaluated, the child region does not have the count property, so it reads from the parent region.

<input type="text" ng-model="count"> binds the input value to the count property in the area of ​​the child objects. As soon as you enter something, the property will be created if it has not already been. From now on, on <h3>Inner {{count}}</h3> gets its value from the content area.

Areas in angular are simple JavaScript objects and are linked to their parents via prototypes. So before you enter something, the content area looks something like this:

 { prototype: { // = parent scope count: 1 } } 

When you change the value to, say, 5, the scope is something like

 { count: 5, prototype: { // = parent scope count: 1 } } 

Since data binding does something like scope.count = 5 .

+1
source share

It seems we cannot override this, since ngTransclude will use the $transclude function directly. See: https://github.com/angular/angular.js/blob/master/src/ng/directive/ngTransclude.js

and: http://docs.angularjs.org/api/ng . $ compile

transcludeFn is a forwarding binding function previously bound to the correct transclusion region. The scope may be overridden by the optional first argument. This is the same as the $ transclude parameter for directory controllers. function ([scope], cloneLinkingFn).

0
source share

All Articles