AngularJS Load controllers only when parent controller finishes async call

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

I am building a website with AngularJS and Google Cloudendpoint

The architecture is as follow. I have :

1 – a MainCtrl that manages the overall site display (top menus, modals, etc…)

<body ng-controller="MainCtrl" ng-cloak main-directive>
    ...
    <div ng-view></div>
    ...
</body>

2 – ng-view with a controller for each page that all have a their own controllers managed by the $routeprovider

main.js

.config([
    '$routeProvider',
    '$locationProvider', 
    '$httpProvider',
    function($routeProvider, $locationProvider, $httpProvider) {

                    ...
        $routeProvider.when('/example', {
            templateUrl : 'partials/example.html', 
            controller  : 'ExampleCtrl',
            access      : accessLevels.PUBLIC
                    }
                    ...
        $routeProvider.otherwise({redirectTo: '/'});
    }

The MainCtrl needs to do an async call to a Google Cloud Endpoint in order to get the session looking like this.

gapi.client.myapi.app.init().execute(function(resp){});

So far what I have done is wrap that code in a Service function called by the MainCtrl then do a watch in every children controller on an initDone value that gets updated in the MainCtrl when the async call is done.

MainCtrl

SessionService.init().then(function(data){
    $scope.initDone = SessionService.getInitDone();
});

Service

angular.module('myapp.services').service('SessionService', function ($http, $q, $rootScope) {

this.initDone = false
this.session = {}

this.init = function(){
    var s = this; 
    var deferred = $q.defer();
    gapi.client.myapi.app.init().execute(function(resp){
        deferred.resolve(resp);
        s.initDone = true;
    });
    return deferred.promise;
}

this.getInitDone = function(){
    return this.initDone;
}

});

ExampleCtrl

angular.module('myapp.controllers').controller('ExampleCtrl', function MainCtrl($scope, $timeout, $log, $location, SessionService) {

$scope.$watch('initDone', function(newVal, oldVal){
    $scope.testValue = SessionService.getInitDone()
})

});

While this work I am not sure it is the right solution. What I would like would be to be able to keep the ng-view waiting until the MainCtrl has done his job.

Because doing the way I do it means that ng-view gets displayed then once the call to the endpoint gets updated which does not look too good from a user prospective.

I tried to play with the resolve in the $routeProvider and do the API call from there but then my MainCtrl does not get the session values properly.

What would be my solution would be to do a :

   $routeProvider.when('/example', {
templateUrl : 'partials/example.html', 
controller  : 'ExampleCtrl',
access      : accessLevels.PUBLIC,
    resolve     : MainCtrl.init
}

But MainCtrl is not available in the $routeProvider

Another idea would be to intercept the ng-view call but I am not sure where to do this and have access to Service at the same time.

Thanks for any idea/help

EDIT

I tried to do something like this in my main.js

        $routeProvider.when('/example', {
            templateUrl : 'partials/example.html', 
            controller  : 'ExampleCtrl',
            access      : accessLevels.PUBLIC,

            resolve: {
                sessionData: ["$q", "SessionService", function($q, SessionService) {
                    //console.log(SessionService.getTest())

                    var deferred = $q.defer();
                    gapi.client.myapi.app.init().execute(function(resp){
                        deferred.resolve(resp);
                    });
                    return deferred.promise;
                }]

        });

Which works if I want each children controller to call to the api. What I would like though is have the children controllers to use whatever the MainCtrl has initialized once it is ready. Basically only load the views and associated controller when MainCtrl is ready (async call included

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

Edited based on comments below

The idea is to block off the view with an ng-if until the api call has returned:

// Session service
 this.init = function(){
      if (!this.initDone){
          var s = this;
          var deferred = $q.defer();
          gapi.client.myapi.app.init().execute(function(resp){
              deferred.resolve(resp);
              s.initDone = true;
          });
          return deferred.promise;
      }
 }




// MainController.js :
 $scope.initDone = false;
 SessionService.init().then(function(
     $scope.initDone = SessionService.getInitDone();
  });


// index.html: 
<div ng-if="initDone"> 
    <div id="view" ng-view> </div>
</div> 

Old answer:
Could you move the async call to Google to a service and have that service return a promise

     myApp.service('DataService', ['$rootScope', '$http', function ($rootScope, $http) {

     var promise = $http.get("http://google.com/theservice").success(function (data) {
             ......
      });
      this.promise = promise

In your routing setup then have a resolve which will wait for the only load the controller after the call to the Google service has returned

    when('/example', {templateUrl: 'partials/example.html', 
                   controller: 'ExampleCtrl', 
         resolve: {
            'MyServiceData': function (DataService) {
                return DataService.promise;
            }
        }}).

Edit: Have a look here how to return a promise for an api call https://stackoverflow.com/a/16117041/514463

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