Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular ui-router: stop controller from reloading on press of back button in the browser

It seems, by default, the controller of the previous state is reloaded when you press the back button in the browser to go to a previous state. (this is not true in case of parent-child states)

How can I prevent that from happening?

Since I am not going to change any data in my current state which can affect the previous state, I don't want the previous state to reload again.

Here is a small plunker: http://plnkr.co/edit/xkQcEywRZVFmavW6eRGq?p=preview

There are 2 states: home and about. If you go to about state and then press back button, you will see that the home state controller is called again.

.state('home', {
    url: '/home',
    templateUrl: 'partial-home.html',
    controller: function($scope) {
       console.log('i was called');
    }
})

I believe this is the expected behavior, but I want to prevent it because my previous state (home in this case) is doing some visualizations which take some time to be created again.

like image 613
gaurav5430 Avatar asked Dec 18 '15 11:12

gaurav5430


1 Answers

Let's start with a global controller like GlobalCtrl which is added to the <body> or <html> tag like ng-controller="GlobalCtrl.

Doing this will enable us to keep the scope of this GlobalCtrl throughout your single page Angular app (as you are using ui-router).

Now, inside your GlobalCtrl define something like this:

$rootScope.globalData = {preventExecution: false};

// This callback will be called everytime you change a page using ui-router state
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) {
    $scope.globalData.preventExecution = false;

    // Just check for your states here
    if (toState.name == "home" && fromState.name == "about") {
        $scope.globalData.preventExecution = true;
    }
});

Now, in your state configuration, you can use this $scope.globalData.preventExecution;

.state('home', {
    url: '/home',
    templateUrl: 'partial-home.html',
    controller: function($scope) {
       if ($scope.globalData.preventExecution) {
           return;
       }
       console.log('i was called');
    }
});

Answer to the question: The scope that we refer in the GlobalCtrl and the scope that we use in the State controller, how are they related?

Well, it is a very good question but it's simple. Every time a new scope is created in Angular, it always inherits its parent scope (unless isolated). So when your home state controller instantiated, its scope created using parent state i.e. $rootScope here in this case and we are instantiating the globalData in the $rootScope which is an Object (an Object in Javascript can be used to it's any nested object. Read this). So now when we are setting the globalData.preventExecution true/false, the same data can be used in the $scope of your home state controller. This is how both scopes are related or using the same data.

Answer to the question: is there some flag or setting in the ui-router which can accomplish this in general

If you want to achieve the above behaviour code for multiple states then you can write something like this:

$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) {
    $scope.globalData.preventExecution = false;

    if (toState.name == "home" && fromState && fromState.preventHomeReExecution) {
        $scope.globalData.preventExecution = true;
    }
});

Now, your states can be written like this:

.state('about', {
    url: '/about',
    templateUrl: 'partial-about.html',
    preventHomeReExecution: true
})
.state('foo', {
    url: '/foo',
    templateUrl: 'partial-foo.html',
})
.state('bar', {
    url: '/bar',
    templateUrl: 'partial-bar.html'
    preventHomeReExecution: true
})

Basically, we are using preventHomeReExecution: true as a flag you wanted.

like image 185
Shashank Agrawal Avatar answered Sep 25 '22 19:09

Shashank Agrawal