I suspect this is due to the nature of how scopes inherit from each other, and so you should use objects on the scope, and not primitives, and always pass the objects into directives via attributes if it's to be then to be used in another scope. So instead of:
$scope.test = "test";
and
<wrapper-directive ng-model="test" some-condition="true">
use:
$scope.userInput = {
test: "Test"
}
and
<wrapper-directive user-input="userInput" some-condition="true">
Which can be seen at http://jsfiddle.net/4RBaN/1/ (I've also changed all-but-one of the ngModels to another custom attribute, as if you're not using ngModel specific things, like ngModelController, or integration with ngForm, then I think better to KISS).
The reason is that if you have 2 scopes in a parent/child relationship (via prototypical inheritance), such as the one created by ngIf
, if you execute (or if Angular executes):
$parentScope.test = 'test';
$childScope.test = 'My new value';
Then $parentScope.test
will still equal 'test'
. However, if you use objects:
$parentScope.userInput = {test: 'test'}
$childScope.userInput.test = 'My new value';
Then $parentScope.userInput.test
will then equal 'My new value'
.
The standard quote from (whom I believe was) the original author of Angular, is "If you haven't got a dot in your model, you're doing it wrong". There are other questions on SO about this, such as If you are not using a .(dot) in your AngularJS models you are doing it wrong?
Edit: If you do want to use ng-model, and always pass a primitive to the directive, and not an object, then there is a way that allows this. You'll need to:
Always ensure there is a dot in the model that is passed to ngModel
, in every directive. Each directive therefore needs to create a new object on its scope in its controller (or linking function). So the template for your 'middle' directive will be:
<input-directive ng-model='userInput.test'>
And in its controller:
$scope.userInput = {
test: $scope.ngModel
}
Where ngModel is bound to the value specified in the directive's attributes:
"scope" : {
"ngModel" : "="
}
Set up manual watchers in each directive, so that changes to the model from the inner directives get propagated back up the chain:
$scope.$watch('userInput.test', function(newTest) {
$scope.ngModel = newTest;
});
This can be seen at: http://jsfiddle.net/zT9sD/ . I have to say, it feels a bit complicated, so I'm not quite sure I can recommend it. Still, (in my opinion) better than using $parent
, as introducing a new scope can quite easily happen as things get more complicated, and then you'll have to use things like $parent.$parent
.