Ensuring one AngularJS controller loads before another

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

I’ll preface this with the fact that I really have no idea if this is the best way to achieve what I’m doing and I’m very open to better suggestions.

I’ve got a user account system using OAUTH2 which gets looks up user info in my database and saves it as a variable, $rootScope.userInfo. This resides in a controller which is appended to my app’s body; here I was thinking that the highest-level controller would load before those that live within it, but apparently not.

If I load a view which tries to access this $rootScope.userInfo object before my mainCtrl has had a chance to load it in from my database, it throws a javascript error and Angular breaks.

For reference, here’s a rough idea of the template:

<body ng-controller="mainCtrl">
    <header>Content</header>
    <div class='main-view' ng-controller="userProfile">
        <p>{{user.name}}</p>
        <p>{{user.email}}</p>
    </div>
</body>

I’m loading $rootScope.userInfo in mainCtrl like this:

$http.get('/api/users/' + $cookies.id).
    success(function(data) {
      $rootScope.userInfo = data.user[0];
      console.log("User info is:");
      console.log($rootScope.userInfo);
      $scope.status = 'ready';
    });

Then for my userProfile control, I do:

function userProfile($scope, $cookies, $http, $routeParams, $rootScope){
  if($scope.status == 'ready'){
    $scope.user = $rootScope.userInfo;
    console.log("Double checking user info, here it is:");
    console.log($rootScope.userInfo);
  }
}

If I’m coming from a different page in the app which doesn’t call on $rootScope.userInfo, the API has enough time to look it up and my userProfile page works fine. However if doing a full-page refresh, the $rootScope.userInfo doesn’t have time to load and I get errors.

How can I fix this?

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

The problem you describe is one of the reasons why it is not recommended to share data between controllers using $rootScope: it creates a manual “invisible” dependency between two controllers, that you have to manually fix when the end user hasn’t gone through the other controller yet.

The recommended solution is to move the user loading logic into a service, say userProfileService, which you inject into both controllers that need it. It will then be created once, and used for both controllers. In such a service you could load the user profile with $http when a controller asks for it, and return it from cache when the next one does. This way, the dependency goes from both controllers to a shared service, rather than from one controller to another.

I’m not a huge fan of the AngularJS documentation, but these might help: DI, Creating Services, and Injecting Services.

Method 2

Use then instead of success and delay the loading of the child controller using ng-include:

<body ng-controller="mainCtrl">
    <header>Content</header>
    <ng-include src="templatePath"></ng-include>        
</body>

Move the HTML inside a new template userprofile.html:

<div class='main-view' ng-controller="userProfile">
    <p>{{user.name}}</p>
    <p>{{user.email}}</p>
</div>

Callback inside the mainCtrl:

$http.get('/api/users/' + $cookies.id).
    then(function(data) {
        /* mainCtrl being the parent controller, simply exposing the 
           properties to $scope would make them available 
           to all the child controllers.*/
        $rootScope.userInfo = data.user[0];
        /* assign the template path to load the template and 
           instantiate the controller */
        $scope.templatePath = "userprofile.html";
    });

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