I prepared a little fiddle and boiled it down to the minimum:
http://jsfiddle.net/lpeterse/NdhjD/4/
<script type="text/javascript">
angular.module('app', ['ui.bootstrap']);
function Ctrl($scope) {
$scope.foo = "42";
}
</script>
<div ng-app="app" ng-controller="Ctrl">
1: {{foo}}<br />
2: <input ng-model="foo" />
<tabs>
<pane heading="tab">
3: {{foo}}<br />
4: <input ng-model="foo" />
</pane>
</tabs>
</div>
In the beginning all views reference the model Ctrl.foo
.
If you change something in input 2:
it properly updates the model and this change gets propagated to all views.
Changing something in input 4:
only affects the views included in the same pane. It behaves like the scope somehow forked. Afterwards changes from 2:
don't get reflected in the tab anymore.
I read the angular docs on directives, scopes and transclusion, but couldn't find an explanation for this undesired behaviour.
I would be grateful for any hints :-)
The problem is the same as in ng-repeat when you edit a primitive - the <pane>
directive creates a new scope which inherits from the parent.
Now, given the way Javascript inheritance works the <pane>
directive has its own copy of the foo
string primitive, and when you edit it you are only editing it on the pane child scope.
A simple solution would be to put foo
in an object on your parent Ctrl:
function Ctrl($scope) {
$scope.data = { foo: 42 };
}
Then you can do this in your HTML:
<tabs><pane><input ng-model="data.foo"></pane></tabs>
Why does it work with an object? Because when <pane>
inherits the parent's scope, its reference to data
will refer to the same object in memory as on the parent Ctrl. Primitives like strings and numbers are copied in inheritance, and objects simply create a new pointer to the same object.
TL;DR: <pane>
's new scope inherits the foo
string primitive as a new copy of foo
which when edited won't change on the parent Ctrl. <pane>
's new scope would inherit an object like data
as a reference to the same object, and when edited on the <pane>
scope the same object would be referenced on the parent scope.
Helpful article: https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance
The <tabs>
and <pane>
directives each create a new transcluded child scope (because they both have transclude: true,
) which prototypically inherits from the parent scope, and an isolate child scope which does not prototypically inherit from the parent scope. The <input...>
inside the <pane>
uses the transcluded child scope.
When the input
inside the <pane>
is first rendered, it is populated with the value of $scope.foo
.
Normal JavaScript prototypal inheritance comes into play here... initially foo
is not defined on the transcluded child scope (prototypal inheritance does not copy primitives), so JavaScript follows the prototype chain and looks at the parent object/$scope, and finds it there. 42
is put into the textbox. The transcluded child scope is not affected/changed (yet).
If you edit the first textbox, the second textbox is updated because JavaScript is still using prototypal inheritance to find the value of $scope.foo
.
If you edit the second textbox, to say 429
, Angular writes the value to $scope.foo
, but note that $scope is the transcluded child scope. Since foo
is a primitive, it creates a new property on that child scope -- that's how JavaScript works, for better or worse. This new property will shadow/hide the parent scope property of the same name. Prototypal inheritance is not in play here. (The article Andy mentions in his post (which is also on SO) also explains this in detail, with pictures.) Since the transcluded child scope now has a foo
property, it will now use that local property for reading and writing, so it appears "disconnected" from the parent scope.
Using an object (rather than a primitive) solves the problem because prototypal inheritance is then always in play. The transcluded child scope gets a reference to the object in the parent scope. Writing to data.foo
writes to the data
object on the parent, not the transcluded child scope.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With