3
votes

I have an example here. How i can access 'itemId' parameter in the parent state controller? Parent view must not be reloaded.

angular.module('app',['ui.router'])
.config(function($stateProvider){
   $stateProvider.state('step', {
            url: '/orders/:step',
            templateUrl: 'parent.html',
            controller: function ($scope, $stateParams) {
                $scope.itemId = $stateParams.itemId;
                $scope.step = $stateParams.step;
            }
        })

        .state('step.item', {
            url: '/:itemId',
            templateUrl: 'child.html',
            controller: function ($scope, $stateParams) {
                $scope.itemId = $stateParams.itemId;
                $scope.step = $stateParams.step;
            }

        });
}).controller('SimpleController', function($scope, $state){
  $scope.items = [1,2,3,4,5,6];
  $scope.steps = ["first", "second"];
})

I see only two ways:

  1. add an service and inject it to both controllers
  2. access a parent scope from a child controller and pass a parameter

    But both cause me to add watchers at a parent scope. Maybe i can accomplish it easier?
4

4 Answers

4
votes

First, you need to recognize that a parent state's controller runs only once when you enter the subtree of its child states, so switching between its children would not re-run the controller.

This is to say that if you want the itemId parameter to always be up to date, you'd need a $watch (which you tried to avoid).

For the first time, you could collect the itemId like so in the parent's state:

$scope.itemId = $state.params.itemId;

If you need it to be kept up-to-date, then you'd need to watch for changes, for example:

$scope.$watch(function(){
  return $state.params;
}, function(p){
  $scope.itemId = p.itemId;
});

plunker

Similarly, if you only need to place it in the view, then set $state on the scope:

$scope.$state = $state;

and in the view:

<div>parent /orders/{{step}}/{{$state.params.itemId}}</div>

EDIT:

I guess another way would be to call a scope-exposed function on the parent to update the value. I'm not a fan of such an approach, since it relies on scope inheritance (which is not always the same as state inheritance) and scope inheritance in a large application is difficult to track. But it removes the need for a $watch:

In the parent controller:

$scope.updateItemId = function(itemId){
   $scope.itemId = itemId;
};

and in the child controller:

if ($scope.updateItemId) $scope.updateItemId($stateParams.itemId)
3
votes

I can see, that you've already found your answer, but I would like to show you different approach. And I would even name it as "the UI-Router built in approach".

It has been shown in the UI-Router example application, where if we go to child state with some ID = 42 like here we can also change the other, than the main view (the hint view in this case).

There is a working example with your scenario, showing that all in action.

What we do use, is the Multiple Named Views

The parent state, now defines root view, which is injected into unnamed ui-view="" inside of the root/index.html.

A new parent.html

<div ui-view="title"></div>
<div ui-view=""></div>

As we can see, it also contains another view target (than unnamed) - e.g. ui-view="title" which we fill with another template immediately... in parent state:

$stateProvider.state('step', {
  url: '/orders/:step',
  views : {
    '': {
      templateUrl: 'parent.html',
    },
    'title@step': {
      templateUrl:'parent_title_view.html',
    }
  }
})

And child? It can continue to handle main area... but also can change the title area. Both views are independent, and belong to child.

.state('step.item', {
  url: '/:itemId',
  views : {
    '': {
      templateUrl: 'child.html',
    },
    'title': {
      templateUrl:'child_title_view.html',
    }
  }

So, there are no watchers, nothing... which is not shipped with the UI-Router by default. We just adjust many places (views) with the child implementation. We can still provide their content with some defaults in parent..

Check that example here. Hope it will help to see it a bit differently...

0
votes

I'm not sure if this will really solve your problem but you can do

   $stateProvider.state('step', {
            url: '/orders/:step/:itemId',
            templateUrl: 'parent.html',

http://plnkr.co/edit/sYCino1V0NOw3qlEALNj?p=preview

This means the parent state will collect the itemID on the way through.

0
votes

include $state in resolve function for parent state

.state('parent', {
                url: '/parent',
                controller: 'parentController',
                resolve: {
                params: ['$state', function ($state) {
                            return  $state.params;
                        }]
                }
            })

then in parent controller inject the resloved vars

app.controller('parentController', ['$scope', 'params',
function ($scope, params) {

  $scope.params = params; 

}]);

in this way params available in both child and parent.