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.

evkline
  • 1,451
  • 3
  • 16
  • 34
  • Investigate `options`, in particular `reload` and `reloadOnSearch` of [state provider](http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state) – Kirill Slatin Apr 06 '15 at 03: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? – evkline Apr 06 '15 at 03: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 – Kirill Slatin Apr 06 '15 at 03:54

1 Answers1

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

Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • 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](https://github.com/angular-ui/ui-router/issues/582#issuecomment-78135352) 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. – evkline Apr 06 '15 at 14:29
  • 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! – evkline Apr 06 '15 at 14:34
  • 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](http://stackoverflow.com/q/24526801/1679310) I created **updated, extended plunker here: http://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 – Radim Köhler Apr 06 '15 at 15:04
  • To be sure *(as my [updated plunker shows](http://plnkr.co/edit/oogTaEDIIA7KGlm6IztS?p=preview))* - 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 Köhler Apr 06 '15 at 15:07
  • 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. – evkline Apr 07 '15 at 15:35
  • Radim, I added a listener to make sure I was changing state and it is firing as expected, however, the listeners for ```$scope.$on('$destroy)``` are not firing in controllers at any level. – evkline Apr 07 '15 at 15:48
  • Wish to help, but as my plunker shows (and my experience) it is "almost" impossible. I would suggest: Create (or adjust mine) plunker, and show me the issue. I am ready to assist... even later ;) – Radim Köhler Apr 07 '15 at 15:59
  • Radim, I did not mean to say your plunker was incorrect. What I'm trying to say is that I am changing states, but the ```$destroy``` function is never being called. I was asking what could be causing this, since I see that you are correct in saying that I shouldn't need to call ```reload: true```. – evkline Apr 07 '15 at 16:06
  • Maybe I do not read your issue properly. But I added this `$scope.$on('$destroy', function(){alert(...)})` into our controller and you can check here, that destroy is fired (on state leave) http://plnkr.co/edit/akKKxf6vSMisPMndMANe?p=preview. But.. In case you are not still happy, please, issue new question. Try to provide some plunker and describe the issue with an example. I am sure you will get your answer here *(I won't touch it... to be sure that someone else will)* – Radim Köhler Apr 07 '15 at 16:16
  • Radim, I also did the same thing to your plunker and noticed ```$scope.$destroy``` was fired. Thats why I was asking what could be preventing this from happening in my application. As you described, this is the expected behavior from ui-router, however, I'm unable to achieve that behavior. I am achieving $state change, but not $scope.$destroy without the ```reload: true``` option. Any ideas what could be causing this? – evkline Apr 07 '15 at 16:18
  • Radim I got it to work... will add to update in question above. – evkline Apr 07 '15 at 16:22
  • Great to see that! ;) ;) – Radim Köhler Apr 07 '15 at 16:23