1

I have a repeated directive which inherits the scope from it's parent controller.

    <div ng-controller="myController">
        {{ message }}
        <person ng-repeat="person in persons"></person>
    </div>

 

app.controller('myController', function ($scope) {
    $scope.message = "A";
    $scope.persons = { 1: {}, 2: {}, 3: {} }
});

But when I change the scope inside the directive, the parent scope does not update.  

app.controller('PersonController', function ($scope) {
    $scope.message = "B";
});

app.directive("person", function () {
    return {
        restrict: 'E',
        transclude: true,
        controller: 'PersonController',
        template: '{{ message }}'
    };
});

http://jsfiddle.net/hientc/L8xo9338/1/

This only happens when I have an ng-repeat on the directive. If I remove the ng-repeat, the parent scope is updated when the directive scope is updated.

How do I make two-way binding work for ng-repeat?

user3213420
  • 351
  • 1
  • 5
  • 11

3 Answers3

3

Change instances of message to message.value.

HTML:

<div ng-app="myApp">
    <div ng-controller="myController">
        {{ message.value }}
        <person ng-repeat="person in persons"></person>
    </div>
</div>

JS:

// Inside myController

$scope.message = {
    value: "A"
};
// Inside PersonController
app.controller('PersonController', function ($scope) {
    $scope.message.value = "B";
});

http://jsfiddle.net/L8xo9338/2/

This has to do with how scopes inherit using javascript's prototype. See What are the nuances of scope prototypal / prototypical inheritance in AngularJS? for an explanation.

Essentially, when you create an object, the scopes reference the same object so it will be updated.

This could be done alternatively with $scope.$parent.message = "B";. However, to me it is less clean.

You can even bind this to a model e.g. <input ng-model="message.value"> and it will update in the parent and in the child.

Community
  • 1
  • 1
soktinpk
  • 3,778
  • 2
  • 22
  • 33
  • Do not know why you got a downvote. this is way better than using `$parent` , which is a bad practice anyways.+1 – PSL Oct 28 '14 at 01:11
0

I think your issue is related to the prototypical inheritance of JavaScript, which behaves differently when you are reading or writing members:

Your directive creates a new scope and tries to set the message member on it. As the member does not exist, it is created on that scope (and not, as you might think, be set on the parent scope which already has this member). This means the message member is created on the "wrong" scope (for what you are trying to do).

The write-behavior is different than the read behavior. If you are trying to access a property from a child scope on the parent scope, you will bubble up, until the property is found, otherwise it will be undefined.

This can be solved by setting the value via a method. This method should be placed on the scope which has the member which needs to be set. In your case something like this should work:

On myController:

$scope.SetMessage = function(m)
{
    $scope.message = m;
}

And PersonController should be changed as follows:

app.controller('PersonController', function ($scope)
{
    $scope.SetMessage("B");
});

You could also access the parent scope via $scope.$parent which might work for the moment, but break as soon as you add/remove a scope (e.g. by nesting it in another directive). Using a method is more flexibel. By the way: that's why it's not working with ng-repeat, as this creates a new scope for every item.

PzYon
  • 2,963
  • 3
  • 16
  • 27
  • 1
    But the only issue with this is that you can't bind it to a model. E.g. `` or `` still won't work, and you end up using ugly `$watch`es. – soktinpk Oct 27 '14 at 23:58
  • Yep, that's true. Good point. Your solution solves this more elegantly. – PzYon Oct 28 '14 at 00:05
0

First of all, your person directive does NOT create any new scope, it reuses the same scope as its parent. That is why your directive works when you didn't use ng-repeat

Second of all, ng-repeat creates new scopes. According to ng-repeat document

Each template instance gets its own scope, where the given loop variable is set to the current collection item

So when you use ng-repeat, even though your directive still doesn't create new scope, it is sharing a new scope created by ng-repeat. That is why setting the message in your directive doesn't affect the parent scope anymore.

Hope this helps! :)

Liang Zhou
  • 2,055
  • 19
  • 20