AngularJS: Call a particular function before any partial page controllers

All we need is an easy explanation of the problem, so here it is.

I want to call a particular function: GetSession() at the beginning of my application load. This function makes a $http call and get a session token: GlobalSessionToken from the server. This session token is then used in other controllers logic and fetch data from the server. I have call this GetSession()in main controller: MasterController in $routeChangeStart event but as its an asynchronous call, my code moves ahead to CustomerController before the $http response.

Here is my code:

var GlobalSessionToken = '';  //will get from server later 

//Define an angular module for our app 
var myApp = angular.module('myApp', ['ngRoute']); 

//Define Routing for app 
myApp.config(['$routeProvider', function ($routeProvider) { 
    $routeProvider. 
      when('/customer', { 
          templateUrl: 'partials/customer.html', 
          controller: 'CustomerController', 
          resolve: { 
            loadData: function($q){ 
                return LoadData2($q,'home'); 
            } 
          } 
      }). 
      otherwise({ 
          redirectTo: '/home'
      }); 
}]); 

//controllers start here and are defined in their each JS file 
var controllers = {}; 

//only master controller is defined in app.js, rest are in separate js files
controllers.MasterController = function($rootScope, $http){
    $rootScope.$on('$routeChangeStart', function(){

        if(GlobalSessionToken == ''){
            GetSession();
        }

        console.log('START');
        $rootScope.loadingView = true;
    });

    $rootScope.$on('$routeChangeError', function(){
        console.log('ERROR');
        $rootScope.loadingView = false;
    });
};

controllers.CustomerController = function ($scope) { 
    if(GlobalSessionToken != ''){
        //do something
    }
} 

//adding the controllers to myApp angularjs app 
myApp.controller(controllers); 
//controllers end here 


function GetSession(){
    $http({
        url: GetSessionTokenWebMethod,
        method: "POST",
        data: "{}",
        headers: { 'Content-Type': 'application/json' }
    }).success(function (data, status, headers, config) {
        GlobalSessionToken = data;
    }).error(function (data, status, headers, config) {
        console.log(data);
    });
}

And my HTML has following sections:

<body ng-app="myApp" ng-controller="MasterController">
    <!--Placeholder for views-->
    <div ng-view="">
    </div>
</body>

How can I make sure this GetSession() is always called at the very beginning of my application start and before any other controller calls and also called only once.

EDIT: This is how I added run method as per Maxim’s answer. Still need to figure out a way to wait till $http call returns before going ahead with controllers.

//Some initializing code before Angular invokes controllers
myApp.run(['$rootScope','$http', '$q', function($rootScope, $http, $q) {
   return GetSession($http, $q);
}]);

function GetSession($http, $q){
    var defer = $q.defer();

    $http({
        url: GetSessionTokenWebMethod,
        method: "POST",
        data: "{}",
        headers: { 'Content-Type': 'application/json' }
    }).success(function (data, status, headers, config) {
        GlobalSessionToken = data;
        defer.resolve('done');
    }).error(function (data, status, headers, config) {
        console.log(data);
        defer.reject();
    });

    return defer.promise;
}

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

Even though some of the solutions here are perfectly valid, resolve property of the routes definition is the way to go, in my opinion. Writing your app logic inside session.then in every controller is a bit too much , we’re used such approach too in one of the projects and I didn’t work so well.

The most effective way is to delay controller’s instantiation with resolve, as it’s a built-in solution. The only problem is that you have to add resolve property with similar code for every route definition, which leads to code duplication.

To solve this problem, you can modify your route definition objects in a helper function like this:

function withSession(routeConfig) {
  routeConfig.resolve = routeConfig.resolve || {};

  routeConfig.resolve.session = ['getSessionPromise', function(getSessionPromise) {
     return getSessionPromise();
  }]

  return routeConfig;
} 

And then, where define your routes like this:

$routeProvider.when('/example', withSession({
  templateUrl: 'views/example.html',
  controller: 'ExampleCtrl'
}));

This is one of the many solutions I’ve tried and liked the most since it’s clean and DRY.

Method 2

You can’t postpone the initialisation of controllers.

You may put your controller code inside a Session promise callback:

myApp.factory( 'session', function GetSession($http, $q){
    var defer = $q.defer();

    $http({
        url: GetSessionTokenWebMethod,
        method: "POST",
        data: "{}",
        headers: { 'Content-Type': 'application/json' }
    }).success(function (data, status, headers, config) {
        GlobalSessionToken = data;
        defer.resolve('done');
    }).error(function (data, status, headers, config) {
        console.log(data);
        defer.reject();
    });

    return defer.promise;
} );

myApp.controller( 'ctrl', function($scope,session) {
   session.then( function() {
      //$scope.whatever ...
   } ); 
} );

Alternative: If you don’t want to use such callbacks, you could have your session request synchronous, but that would be a terrible thing to do.

Method 3

You have not provided any details related to GetSession. For scenarios like this you should use the resolve property while defining your routes in $routeProvider. I see you are using resolve already.

What you can do now is to wrap the GlobalSessionToken into a Angular service like GlobalSessionTokenServiceand call it in the resolve to get the token before the route loads. Like

resolve: { 
            loadData: function($q){ 
                return LoadData2($q,'home'); 
            },
            GlobalSessionToken: function(GlobalSessionTokenService) {
                 return GlobalSessionTokenService.getToken()  //This should return promise
            }
         } 

This can then be injected in your controller with

controllers.MasterController = function($rootScope, $http,GlobalSessionToken){

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply