0

Plunker here.

I have a directive ("child") nested inside another directive ("parent"). It requires ngModel, and ngModelCtrl.$modelValue is shown and kept up-to-date just fine in its template. That is, until I call ngModelCtrl.$setViewValue().

So here is the HTML initialising the directives:

<div parent>
  <div child ng-model="content">Some</div>
</div>

And here are the directives:

angular.module('form-example2', [])
  .controller('MainCtrl', function($scope){
  $scope.content = 'Hi';
})
.directive('parent', function() {
  return {
    transclude: true,
    template: '<div ng-transclude></div>',
    controller: function(){
    },
    scope: {}
  };
})
.directive('child', function() {
  return {
    require: ['ngModel', '^parent'],
    transclude: true,
    template: '<div>Model: {{model.$modelValue}} (<a style="text-decoration: underline; cursor: pointer;" ng-click="alter()">Alter</a>)<br />Contents: <div style="background: grey" ng-transclude></div></div>',
    scope: {},
    link: function(scope, elm, attrs, ctrl) {
      var ngModelCtrl = ctrl[0];
      var parentCtrl = ctrl[1];

      scope.model = ngModelCtrl;

      // view -> model
      scope.alter = function(){
        ngModelCtrl.$setViewValue('Hi2');
      }

      // model -> view

      // load init value from DOM
    }
  };
});

When the model (i.e. content) changes, this change can be seen inside the child directive. When you click the "Alter" link (which triggers a call of $setViewValue()), the model's value should become "Hi2". This is correctly displayed inside the child directive, but not in the model outside the directive. Furthermore, when I now update the model outside the directive, it is no longer updated inside the directive.

How come?

Vincent
  • 4,876
  • 3
  • 44
  • 55

1 Answers1

0

The directives ended up being just fine; the only problem was that the passed model should be an object property. Hence, the directives work if the following modifications are made to the calling code (Plunker):

In the controller, instead of $scope.content = 'Hi';:

$scope.content = {
  value: 'Hi'
};

In the template, replace all references to content with content.value:

<input ng-model="content.value" type="text" />
<div parent>
  <div child ng-model="content.value">Some</div>
  </div>
<pre>model = {{content.value}}</pre>

The reason this works, roughly, is that when Angular passes the reference to the model to the transcluded scope of the parent directive (i.e. the one the child is in), this is only a reference when it refers to an object property - otherwise it is a copy, which Angular cannot watch for changes.

@Josep's answer helped greatly so, even though it did not provide the actual solution, if you're reading this and it's useful, give it a vote :)

Vincent
  • 4,876
  • 3
  • 44
  • 55
  • Thanks for the update. So if that statement is wrong, what then is the reason that changing it as shown above solves it? Also, when ngModel binds to the "current" scope, is that in this case not a child of the scope of the controller? Since the transclusion of the parent directive makes the scope of the transcluded content a sibling of the scope of the parent directive. And that should mean that `content` still refers to the model of the controller, right? – Vincent Oct 30 '14 at 14:17
  • I definitely want to understand, that is why I'm asking all these questions :) So what you're saying about the `scope.model = ngModelCtrl;` makes sense. However, when I do not do that (which is cleaner indeed), it still doesn't work with primitive models: http://plnkr.co/edit/oAJ7cvWSj1PmHbA7VL36?p=preview Do you have any idea why that is the case? Also, did you delete your answer? :( – Vincent Oct 30 '14 at 14:55