2

I'm trying to figure out how to resolve a promise before creating my topmost controller. I'm using ui.router so I know how to resolve promises for the controllers for my different routes. But I don't know how to resolve it before my first controller gets loaded.

This is an extract of my index.html:

<html lang="de" x-ng-app="myapp">
    <body x-ng-controller="MainController">
        ...
        <div id="header" x-ng-include="'top.html'"></div>
        ...
        <div id="viewDiv" ui-view></div>
        ...
    </body>
</html>

I want to load the current season (and other data that rarely ever changes) from the server and have it injected into the controller for my top.html header and for some of my view controllers. I have a service that loads my current season and returns a promise:

services.factory('RestAccess', function(Restangular) {
    return {
        loadCurrentSeason : loadCurrentSeason
    };

    function loadCurrentSeason() {
        return Restangular.one('season','current').get();
    }
});

And I'd like to have the RESOLVED promise from this function injected in my MainController:

controllers.controller('MainController', function($scope, currentSeason, SomeService) {
    $scope.season = currentSeason;
    $scope.stuff = SomeService.loadStuff(currentSeason);
});

how can I configure this? This is for the Controller responsible for the body-element. So I'm not using any routing here yet.

I'd like something like

resolve: {
    currentSeason : function(RestAccess) {return RestAccess.loadCurrentSeason();}
}

But I don't know where to put this.

EasterBunnyBugSmasher
  • 1,507
  • 2
  • 15
  • 34
  • why not waiting for your promise to resolve and hide the content or do anything you want with some `ng-if = "currentSeason"` or the like? `loadCurrentSeason.then(function(currentSeason) {$scope.currentSeason = currentSeason; });` You could also have a wrapping directive that waits for the promise and then call the controller by giving it the resolved value by parameter in some "init" method. – floribon Mar 28 '16 at 16:45
  • it's not about hiding content. It's about calling service methods that require this argument. I'd go with your suggestion if it was just in a handful of controllers. But I'll need this value in dozens of controllers. – EasterBunnyBugSmasher Mar 28 '16 at 17:13
  • then you could have your services consume promise rather of value, that's the idea of promise: manipulate them as regular values, and register a `then` when you need their value. Or if this season thing is very specific you could load it first even before you launch the application, and then kicks the compilation with `angular.bootstrap(...)` – floribon Mar 28 '16 at 18:04
  • my problem is that I need to wait for all of 3-4 promises to be able to continue. And that would make the code quite complicated and unreadable. I didn't know about angular.bootstrap(...), I guess that would have been the straight-forward answer I was looking for when I asked the question. I decided to go the other route of including my MainController into my ui.router states. – EasterBunnyBugSmasher Mar 31 '16 at 10:44
  • So actually you want on top.html first load initiate some static data and then reuse for different controllers? – Augustas Apr 01 '16 at 12:41

3 Answers3

2

You can have a resolve for an abstract parent state, which will resolve before the individual, concrete child routes' resolves (and can therefore be used in those resolves).

Example:

//In module.config()...
...
$stateProvider
.state('home', {
    url:'/',
    template: '<div ui-view></div>',
    abstract: true,
    resolve: {
        'parentResolve':['yourService', function(yourService){ /*Return promise*/ }]
    }
})
.state('home.child', {
    url:'/child',
    template: '<div>After home</div>',
    controller: ['parentResolve', function(parentResolve){ /*Use parentresolve*/ }],
    resolve: {
        'childResolve':['parentResolve','yourService', function(parentResolve, yourService){ /*Parent resolve is available, as is service*/ }]
    }        
}
...

Additionally, you can use module.run() to run something before it tries loading any states.

Harris
  • 1,775
  • 16
  • 19
  • currently, the view with my MainController is outside of the $stateProvider's configuration. I guess I would need to change that for your solution? – EasterBunnyBugSmasher Mar 28 '16 at 16:45
  • Yeah, I'd do that. Generally, having the overarching, stateless section outside is fine, but in this case Angular has things within `config` that you need, and you'll still be able to represent that it doesn't belong to one specific state, so I don't think you're really compromising anything. – Harris Mar 28 '16 at 16:57
  • I changed my routing to your suggestion with the abstract home state. It works fine with the exception of one thing: The parentResolve is not resolved when I try to resolve my childResolve (but it is injected correctly into the controller). When I debug it, it's undefined. I have a workaround for that though. – EasterBunnyBugSmasher Mar 31 '16 at 10:40
  • If you don't mind me asking, what workaround do you have? Would you mind posting what you have and what you've come up with? I'd feel bad about giving an answer that needed to be hacked to work, so maybe I can improve it. – Harris Mar 31 '16 at 15:55
  • I let it rest for a day, and now the parent is resolved in the child controller's resolve function. No clue why. The workaround wasn't worth mentioning. – EasterBunnyBugSmasher Apr 01 '16 at 18:30
0

Use a resolve in the routing config

.state('someState',{
   url:'....',
   controller:'MainController',
   resolve:{
      currentSeason:function(SomeService){
         // return the promise needed to resolve for injected variable
         return SomeService.someMethod();
      }
   }

})

I think this is what you are asking. Question is a bit vague

charlietfl
  • 170,828
  • 13
  • 121
  • 150
0

Mr. Easter Bunny,

I had a very similar dilemma. By the way, the problem is not that you want to resolve the promise before your controller loads, but that you want to resolve the promise before your page loads in order to prevent a data-less page from being rendered.

My Solution

  1. First step. You don't want to load the page/template until the promise has resolved. Meaning, until there is data to be shown. For that use ngIf, NOT ngShow, as "ngIf" will only render the page once our condition is true, i.e when there is data present.

  2. Have the 'MainController' maintain a variable that indicates whether or not the data has been loaded (meaning, the promise has resolved), and have ngIf monitor that variable.

For that, create a service (or service model --whatever you want to call it) that holds the data returned by the promise.

angular('app').factory('dataServiceModel', function(){

   var dataModel= this;

   var dataModel.someData = {};

   return dataModel;
});

In the controller, populate the data within the promise callback function and set the variable to let ngIf know that there is data to be shown.

angular('app').controller('MainController', ['dataServiceModel'], function(){

var mainController = this;

mainController.loadedData = false;

mainController.getTheDataFromTheServer(){
      ... 
      ...
      { // Callback to the promise and on successful retrieval
        dataServiceModel.someData = ... the data ..
        mainController.loadedData = true
      }
   }
}); 

Now from the markup;

<ANY ng-controller="MainController as mainController"
  ng-if="mainController.loadedData">
...
</ANY>

I think that might work for you as well.

Wilmer SH
  • 1,417
  • 12
  • 20
  • it's not about the displaying. It's about the Controller code running. For each and every Controller I need a subset of 5-8 very static data (from the Server) to be loaded before I can use these data values as Parameters for subsequent queries. I don't want to program every controller to wait for 3-4 promises to be all resolved before I start the work. I want them injected in the controller in a resolved state. – EasterBunnyBugSmasher Mar 31 '16 at 10:37