I am trying to bind to a property via ngModel in a layer of directives 3 levels deep. This would be fine, except the middle level contains a ng-if
which I believe creates a new scope. The binding is lost at this point.
I have created a jsfiddle to explain the situation: http://jsfiddle.net/5fmck/2/
Note that it works if the ng-if
directive is removed, but I am using ng-if
instead of ng-show
for performance reasons
Does anyone know how I can get the original ngModel to update from the 'inputDirective' template in the fiddle?
Simple :3
Just remember, that child scope is created = use reference to $parent :)
<div ng-if='someCondition'>
<span>In Wrapper</span>
<input-directive ng-model='$parent.ngModel'></input-directive>
</div>
// upd
As I know you need to use reference to $parent only if ngModel is primitive, not object.
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 AngularJS: If you are not using a .(dot) in your 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
.