Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

$onDestroy not called on Controller but $scope.$on($destroy) is #15073

Closed
northkode opened this issue Aug 31, 2016 · 7 comments
Closed

$onDestroy not called on Controller but $scope.$on($destroy) is #15073

northkode opened this issue Aug 31, 2016 · 7 comments

Comments

@northkode
Copy link

northkode commented Aug 31, 2016

Having an issue with the $onDestroy of a Route controller not getting called.

class AccountController {

    constructor($state, $scope, $rootScope) {

        Object.assign(this, { $state,  $scope, $rootScope });

        this.watcher = $rootScope.$on('$stateChangeStart', (event, next, current) => {
            this.currentNavItem = next.url.split('/')[1];
        });

        // IS CALLED.. but looks like memory keeps leaking and doesn't actually destory anything
        $scope.$on('$destroy', () => { 
            this.watcher();
        })

    }

    $onInit() {
        console.log('init');
        this.currentNavItem = null
        this.currentNavItem = this.$state.current.url.split('/')[1];
    }

    // IS NOT CALLED
    $onDestroy() {
        console.log('destroy') 
        this.watcher();
    }

}

AccountController.$inject = ['$state', '$scope', '$rootScope']

export default AccountController;

I can't seem to get the $onDestroy to ever fire.. if i remove the inject of scope that doesn't help either..

I am using angular 1.5.8, this happens on all browsers.

Below is the routes file that i'm using.

This issue happens on all children routes as well.

routes.$inject = ['$stateProvider'];

export default function routes($stateProvider) {
  $stateProvider
    .state('account', {
      url: '/account',
      template: require('./template.html'),
      controller: 'AccountController',
      controllerAs: 'account',
      resolve: {}
  }).state('account.you', {
      url: '/you',
      template: require('./tabs/you.html'),
      controller: 'YouController',
      controllerAs: 'you',
      resolve: {}
  }).state('account.users', {
      url: '/users',
      template: require('./tabs/users.html'),
      controller: 'UsersController',
      controllerAs: 'users',
      resolve: {}
  }).state('account.groups', {
      url: '/groups',
      template: require('./tabs/groups.html'),
      controller: 'GroupsController',
      controllerAs: 'groups',
      resolve: {}
  }).state('account.surveys', {
      url: '/surveys',
      template: require('./tabs/surveys.html'),
      controller: 'SurveysController',
      controllerAs: 'surveys',
      resolve: {}
    })
}
@Narretz
Copy link
Contributor

Narretz commented Aug 31, 2016

I can't see in the example where the controller actually gets destroyed, so it's hard to say where the problem is. Also note that $onDestroy is called when the containing scope is destroyed.

@northkode
Copy link
Author

The controller is setup using controllersAs to avoid injecting scope in.. but when I do inject scope in destroy is called... to me that means the scope of the route is getting killed but not calling the controller $onDestroy.

This route is a main route on the global ui-view of the page. I simply navigate back and forth in these routes but nothing fires.

@dcherman
Copy link
Contributor

This looks like a ui-router problem since $stateProvider was mentioned here. You may want to ensure that they support the new hooks created in 1.5. I recall a similar issue filed a while back with $onInit

@northkode
Copy link
Author

@dcherman $onInit and $onChanges all work and $onDestroy works in other controllers I have.. but on main routes it doesn't.. ui-router definitely supports it. but certain routes dont get called and I'm noticing just root routes. Routes that are top level vs child components and child routes.

Ill do some more investigating.

@northkode
Copy link
Author

So i just did some refactoring and if i Route to a component vs a controller the $onDestroy works

so my state now looks like

export default angular.module('app', [uirouter])
    .config(routing)
    .component('accountComponent', AccountController)
    .component("you", YouComponent)
    .component("users", UsersComponent)
    .component("groups", GroupsComponent)
    .component("surveys", SurveysComponent)
    .component("groupCreate", GroupCreateComponent)
    .name;

now when i navigate to those components based on my stateprovider:

export default function routes($stateProvider) {
    $stateProvider
        .state('account', {
            url: '/account',
            template: "<account-component></account-component>",
            component:"account",
            resolve: {}
        }).state('account.you', {
            url: '/you',
            template: "<you flex layout='column'></you>",
            component: "you",
            resolve: {}
        }).state('account.users', {
            url: '/users',
            template: "<users flex layout='column'></users>",
            component: "users",
            resolve: {}
        }).state('account.groups', {
            url: '/groups',
            template: "<groups flex layout='column'></groups>",
            component: "groups",
            resolve: {}
        }).state('account.creategroup', {
            url: '/groups/create',
            template: "<group-create flex layout='column'></group-create>",
            component: "groupCreate",
            resolve: {}
        }).state('account.surveys', {
            url: '/surveys',
            template: "<surveys flex layout='column'></surveys>",
            component: "surveys",
            resolve: {}
        })
}

all the proper events fire..

@Narretz
Copy link
Contributor

Narretz commented Sep 1, 2016

Can you even reproduce the problem without ui-router?

@gkalpak
Copy link
Member

gkalpak commented Sep 7, 2016

This seems like a duplicate of #14376. There is a misconception that controller.$onDestroy() is equivalent to $scope.$on('$destroy'), where $scope is the scope passed to the controller. This is not always the case.

More specifically, controller.$onDestroy() is called when the scope where the controller "lives in" gets destroyed. When you have an isolate scope directive, the scope passed (as $scope) to the controller is not the scope that the controller lives in, it is a child-scope that is created for the directive's template. Thus, calling $scope.$destroy() will NOT destroy the scope that the controller lives in (and NOT call controller.$onDestroy()).

The only case, where controller.$onDestroy() is equivalent to $scope.$on('$destroy') is when having a directive with scope: false. This means that no new scope is created, so the scope passed (as $scope) to the controller is the same scope that the controller lives in.

Closing since this is working as expected as far as Angular is concerned. If you think there is a bug with ui.router, please submit an issue on their repo (possibly linking to this issue for context).

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants