4

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.

skip
  • 12,193
  • 32
  • 113
  • 153

2 Answers2

5

There is a plunker, which should show that your scenario is working.

The most important part of that solution is driven by:

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.

Community
  • 1
  • 1
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • Hello Radim, here http://plnkr.co/edit/y7BMdyRhj2WvFXq0UUud?p=preview is the plunker I've created of the issue I was facing. – skip Aug 21 '14 at 06:59
  • Thanks, I was creating the plunker so couldn't go through the answer yet. Am doing it now. – skip Aug 21 '14 at 07:01
  • 1
    I decided to create new answer, becuase the content of that is much more complex and different that it would not be suitable for edit. Hope this help. The ui-router is amazing library ;) Enjoy it – Radim Köhler Aug 21 '14 at 07:14
  • Sometimes documentation buries the essential and portrays the unused or rarely used. Very good explanation, must read for all those who use ui-router extensively. – Giridhar Karnik Aug 14 '16 at 18:02
3

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 common denominator

We have to move the shared collection (array bar) into the parent. Why?

we have to move the shared reference to the least common denominator - to the parent scope

see

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:

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

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

Community
  • 1
  • 1
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335