I have a scoped variable $scope.foo
that I am keeping a watch on. It could be updated through a text field in a form.
I have two named views A
and B
on a page that I am rendering using angular ui-router.
The named view A
has the text form field that is being watched for changes in a controller through ng-model="foo"
. When the value of foo
is changed by a user it changes the value of another scoped variable $scope.bar
, which is an array, in the controller that is being used in the ng-repeat
directive on the named view B
. The changes in the $scope.bar
is made using $scope.$watch
method in the controller.
The issue that I am facing is that the when the foo
is changed I could see the changes in bar
on the named view A
but not on the named view B
.
Could somebody help me resolve this issue?
Edit: Here is the plunker for this issue.
I have a scoped variable $scope.foo
that I am keeping a watch on. It could be updated through a text field in a form.
I have two named views A
and B
on a page that I am rendering using angular ui-router.
The named view A
has the text form field that is being watched for changes in a controller through ng-model="foo"
. When the value of foo
is changed by a user it changes the value of another scoped variable $scope.bar
, which is an array, in the controller that is being used in the ng-repeat
directive on the named view B
. The changes in the $scope.bar
is made using $scope.$watch
method in the controller.
The issue that I am facing is that the when the foo
is changed I could see the changes in bar
on the named view A
but not on the named view B
.
Could somebody help me resolve this issue?
Edit: Here is the plunker for this issue.
Share Improve this question edited Aug 21, 2014 at 7:08 skip asked Aug 21, 2014 at 4:45 skipskip 12.7k34 gold badges117 silver badges159 bronze badges 2- Can you just post a test fiddle for the same?? – Manish Kr. Shukla Commented Aug 21, 2014 at 5:00
- @TechMa9iac: Hello Tech here plnkr.co/edit/y7BMdyRhj2WvFXq0UUud?p=preview is the plunker of the issue I have been facing. – skip Commented Aug 21, 2014 at 7:00
2 Answers
Reset to default 5There is a plunker, which should show that your scenario is working.
The most important part of that solution is driven by:
- Scope Inheritance by View Hierarchy Only (cite:)
Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).
It is entirely possible that you have nested states whose templates populate ui-views at various non-nested locations within your site. In this scenario you cannot expect to access the scope variables of parent state views within the views of children states.
Let me express it again: A scope
inheritance goes only via the view
nesting.
With that we can create this states definitions:
$stateProvider
.state('root', {
url: '/root',
templateUrl: 'tpl.root.html',
controller: 'RootCtrl', // this root scope will be parent
})
.state('root.entity', {
url: '/entity',
views:{
'A': {
templateUrl: 'tpl.a.html',
controller: 'ACtrl', // scope is inherited from Root
},
'B': {
templateUrl: 'tpl.b.html',
controller: 'ACtrl', // scope is inherited from Root
}
}
})
So the state defintion support nested views - let's profit from that and place the $scope.bar
collection into the parent. All views involved will then have access to the same collection:
.controller('RootCtrl', function ($scope, $state) {
$scope.bar = ['first', 'second', 'last'];
})
.controller('ACtrl', function ($scope, $state) {
// *) note below
$scope.foo = $scope.bar[0];
$scope.$watch("foo", function(val){$scope.bar[0] = val; });
})
.controller('BCtrl', function ($scope, $state) {
})
*) note: here we do 1) set from bar 2) $watch and 3) set back to bar to follow the question description... but if the array would contain objects, we can work with them directly... without that overhead, but that's another story...
Check here how that works, and that any changes in view A are also visible in B ... because of inherited reference to the array bar
declared in parent $scope.
I created the second answer, to follow also the issue in this plunker, which @skip (OP) passed me as the example fo the issue.
Firstly There is an updated working version
of that plunker, which does what we need. There are the main changes:
The original state
def:
.state('home', {
url: '/',
views: {
'': { templateUrl: 'home.html' },
'A@home': {
templateUrl: 'a.html',
controller: 'MainCtrl'
},
'B@home': {
templateUrl: 'b.html',
controller: 'MainCtrl'
}
}
Was replaced with the RootCtrl
defintion:
.state('home', {
url: '/',
views: {
'': {
templateUrl: 'home.html',
controller: 'RootCtrl' // here we do use parent scoping
},
'A@home': {
templateUrl: 'a.html',
controller: 'MainCtrl'
},
'B@home': {
templateUrl: 'b.html',
controller: 'MainCtrl'
}
}
And this was one controller:
app.controller('MainCtrl', function($scope) {
var fruits = [{"name": "Apple"}, {"name": "Banana"}, {"name": "Carrot"}];
$scope.bar = $scope.bar || [];
$scope.foo = 2;
$scope.$watch('foo',function(value, oldValue){
$scope.bar = [];
getBar(fruits, value);
});
function getBar(fruits, howManyFruits) {
for(var i=0; i < $scope.foo; i++) {
$scope.bar.push(fruits[i]);
}
}
});
But now we do have two (Parent and child):
app.controller('RootCtrl', function($scope) {
$scope.bar = [];
})
app.controller('MainCtrl', function($scope) {
var fruits = [{"name": "Apple"}, {"name": "Banana"}, {"name": "Carrot"}];
//$scope.bar = $scope.bar || [];
$scope.foo = 2;
$scope.$watch('foo',function(value, oldValue){
$scope.bar.length = 0;
getBar(fruits, value);
});
function getBar(fruits, howManyFruits) {
for(var i=0; i < $scope.foo; i++) {
$scope.bar.push(fruits[i]);
}
}
});
Some important parts to mention
I. The least mon denominator
We have to move the shared collection (array bar) into the parent. Why?
we have to move the shared reference to the least mon denominator - to the parent scope
see
- How do I prevent reload on named view, when state changes? AngularJS UI-Router
II. The Reference to array must be unchanged
we have to keep the reference to the Parent $scope.bar
unchanged!. This is essential. How to achieve that? see:
- Short way to replace content of an array
where instead of creating new reference, we clear the array, keeping the reference to it
// wrong
$scope.bar = [];
// good
$scope.bar.length = 0;
III. Controller can have multiple instances
Also, the fact that both views A and B had the same controller (same controller name in fact), definitely did not mean, that they were the same instance.
No, they were two different instances... not sharing anything. That is I guess, the most critical confusion. see
- angularjs guide - Dependency Injection
Controllers are special in that, unlike services, there can be many instances of them in the application. For example, there would be one instance for every ng-controller directive in the template.
Please, observe that all in the updated example