1

I'm attempting to use UI-router to manage state change in my app. I thought that changing states on a dynamic route would cause the current scope to be destroyed and a new scope created for the template being re-inserted with the new content, for example:

$stateProvider
  .state('foo', {
    url: '/:id'
    views: {
      'foo@': {
        templateUrl: 'partials/foo.html',
        controller: 'Foo',
        controllerAs: 'FooCtrl as foo'
      }
    }
  });

I thought the state above would destroy & create the FooCtrl each time the user navigated to a route with a different id. Thus, running the initialization functions located in FooCtrl on each route change to initialize the current view with the right data from the services being injected into the controller. I've been listening in my controllers for the $scope.$destroy function to be run on these state changes, but they aren't called.

What I'm curious about is, what is the idiomatic way to create & destroy controllers to get the functionality I described above? Also, is there a more idiomatic way to achieve the same thing in AngularJS?

Thanks!

UPDATE: In order to destroy and re-create the controller when using $state.go() you must pass the {reload: true} option as the 3rd parameter as below.

$state.go('foo', {id: '1'}, {reload: true})

As Radim stated, there should be no need to call {reload: true} in order for the controller to be re-instantiated. Currently, I'm listening for $stateChangeStart to make sure that the state is actually being updated. After seeing that it is, I'm listening for $scope.$on('$destroy') and it is not firing. So for some reason, state is being changed without the controller being destroyed & re-instantiated.

UPDATE WORKING The error I was making was that in my deepest nested views I was using an absolute path. This seemed to persist the controller from state to state. When I made the nested views relative they are being destroyed and re-created on state change as Radim described.

3
  • Investigate options, in particular reload and reloadOnSearch of state provider Commented Apr 6, 2015 at 3:32
  • Kirill, in the API docs for UI-router it says there is currently a bug that causes reload not to reinstantiate a controller. Currently, there is an open issue on github, although there seems to be a hack to make it work inside a controller. That would mean I'd have to listen for $stateChangeStart in every controller and fire the ```$state.reload()`` from inside each controller. That seems like a lot of overhead, is there a more idiomatic approach? Commented Apr 6, 2015 at 3:48
  • Since there is a bug as you stated there can't be an idiomatic approach. In any case it would be a workaround. I first thought of switching to another state. For example, parent state before switching to state with modified parameter. By default this will force ui-router to deactivate and then activate the state in interest again. But it won't work if users switch the url manually Commented Apr 6, 2015 at 3:54

1 Answer 1

1

There is a working plunker. The UI-Router is by default doing, what you expected. It is re-instantiating controller on every change state, i.e. even if just a parameter (id) is changed

I just a bit adjusted the above state definition:

  // instead of this
  .state('/:id', {
      views: {
        'foo@': {
          templateUrl: 'partials/foo.html',
          controller: 'Foo',
          controllerAs: 'FooCtrl as foo'
        }
      }
    })

  // wee need this
  .state('myState', {
      url: '/:id',
      views: {
        'foo@': {
          templateUrl: 'partials/foo.html',
          controller: 'FooCtrl',
          controllerAs: 'foo'
        }
      }
    })

And this FooCtrl is logging into console any time we navigate to state 'MyState' with different id

// register controller
.controller('FooCtrl', FooCtrl)

// this function will be our controller, called any time new id is passed
function FooCtrl($scope, $stateParams) {
    console.log('init with id: ' + $stateParams.Id + ', state params:')
    console.log($stateParams)

    var foo = {};
    foo.$stateParams = $stateParams
    return foo;
};

FooCtrl.$inject = ['$scope', '$stateParams'];

Check it in action here

Sign up to request clarification or add additional context in comments.

12 Comments

Hey Radim, sorry I forgot to include the url in the question. I'm not sure why I'm not seeing similar behavior. I found this $state.reload workaround as a workaround to my problem. When using $state.go() the controller wasn't reinstantiated, but when using the workaround above $scope.$destroy is fired and the controller is reinstantiated.
I'm changing state with a ng-click opposed to ui-sref and then calling $state.go from within the function that the ng-click fires... am I missing an option to $state.go that doesn't cause the controller to be reinstantiated? Thanks again Radim!
Well, ng-click with internal $state.go is the same. In fact, directve ui-sref is using $state.go behind the scene. Check it here I created updated, extended plunker here: plnkr.co/edit/oogTaEDIIA7KGlm6IztS?p=preview to show you, that it is re-initiating controller every time. The point is we must pass different id, there must be some change... otherwise, state won't be changed. So, double check your params definition, and be sure you are sending some params
To be sure (as my updated plunker shows) - There is no need to explicit call reload: true. NO. If there is a parameter change. If we go from id:1 to id:2 all will work as expected. BUT, if we go from id:1 to id:1 - then in fact we do not change the state. That's why no "recreation of controller" appears. State change means: state (itself) or its params are different. Otherwise, UI-Router is doing its best to avoid re-init. Because it is not a state change. Hope it helps now..
Radim, when I call $state.go without reload set to true the initialization functions in my controller do not fire after their initial load. So in my app if I go from state to state, while things may change in the views, the controllers associated with those views are never destroyed and therefore never re-instantiated to re-run the initialization code. I'm not sure why I'm not seeing the same behavior as your plunkers. Thanks again, for the help, I will further investigate why my app isn't behaving as it should.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.