Need to resolve a $http request before the execution of the resolve property inside $stateProvider
Asked Answered
I

2

6

I’m building an angular application that is going to run on several domains. Since there are different configurations on each domain I'll need to fetch all the variables by doing a call to the server. The call will return a JSON object that contains different rest urls. My problem is that I need to do this call before the 'resolve' step inside the $stateProvider, since I already have a task that is dependent on the configuration object from the server.

Iulus answered 21/3, 2015 at 10:11 Comment(1)
Since I need to fetch the configuration once (and also if the user refreshes with the browser) It would probably be OK to use the run method. But how can I make the ui-router wait until the promise is resolved?Iulus
B
3

What should work here is a really great feature $urlRouterProvider.deferIntercept(); documented here:

$urlRouterProvider

The deferIntercept(defer)

Disables (or enables) deferring location change interception.

If you wish to customize the behavior of syncing the URL (for example, if you wish to defer a transition but maintain the current URL), call this method at configuration time. Then, at run time, call $urlRouter.listen() after you have configured your own $locationChangeSuccess event handler.

The code snippet from the API documentation:

var app = angular.module('app', ['ui.router.router']);

app.config(function($urlRouterProvider) {

  // Prevent $urlRouter from automatically intercepting URL changes;
  // this allows you to configure custom behavior in between
  // location changes and route synchronization:
  $urlRouterProvider.deferIntercept();

}).run(function($rootScope, $urlRouter, UserService) {

  $rootScope.$on('$locationChangeSuccess', function(e) {
    // UserService is an example service for managing user state
    if (UserService.isLoggedIn()) return;

    // Prevent $urlRouter's default handler from firing
    e.preventDefault();

    UserService.handleLogin().then(function() {
      // Once the user has logged in, sync the current URL
      // to the router:
      $urlRouter.sync();
    });
  });

  // Configures $urlRouter's listener *after* your custom listener
  $urlRouter.listen();
});

And also, related to this question:

There is working example - plunker

To make it clear, suitable for this use case, let's observe the code of the plunker.

So, firstly we can see the .config() phase. It does have access to providers but NOT to their services (e.g. $http). Not yet, services themselves will be available later...

app.config(function ($locationProvider, $urlRouterProvider, $stateProvider) 
{
    // this will put UI-Router into hibernation
    // waiting for explicit resurrection later
    // it will give us time to do anything we want... even in .run() phase
    $urlRouterProvider.deferIntercept();
    $urlRouterProvider.otherwise('/other');
    
    $locationProvider.html5Mode({enabled: false});
    $stateProviderRef = $stateProvider;
});

What we did, is set a reference to provider (configurable object), to be used later: $stateProviderRef.

And the most crucial thing is we STOPPED the UI-Router, and forced him to wait for us with $urlRouterProvider.deferIntercept(); (see the doc and cites above)

There is an extract of the .run() phase:

app.run(['$q', '$rootScope','$http', '$urlRouter',
  function ($q, $rootScope, $http, $urlRouter) 
  {

   // RUN phase can use services (conigured in config phase)
   // e.g. $http to load some data
    $http
      .get("myJson.json")
      .success(function(data)
      {

        // here we can use the loaded stuff to enhance our states
        angular.forEach(data, function (value, key) 
        { 
          var state = { ... }
          ...

          $stateProviderRef.state(value.name, state);
        });
        // Configures $urlRouter's listener *after* your custom listener
        
        // here comes resurrection of the UI-Router
        // these two important calls, will return the execution to the 
        // routing provider
        // and let the application to use just loaded stuff
        $urlRouter.sync();
        $urlRouter.listen();
      });
}]);

Most important is, that this .run() was executed just ONCE. Only once. As we require.

We can also use another technique: resolve inside of one super root state, which is parent of all state hierarchy roots. Check all the details here:

Nested states or views for layout with leftbar in ui-router?

Boiardo answered 21/3, 2015 at 11:34 Comment(5)
Actually I need to fetch the configuration only once (and also when the user just refreshes the page). So I need do to the http request before the ui-router resolve and after the angular app is instantiated. So actually the code can be implemented in the run method. But I do not know how I can make angular wait for the http promise to resolve.Iulus
Christian, I would say, I do understand. And exactly that is the content of my answer. Check the plunker please. With a real example....Edraedrea
And in case, that I did not understand - that your question was just about how to use $http in the resolve - I added another anser. Hope it helps ;) If anything, I'am ready to assit...Edraedrea
Thanks a lot!!! This is exactly what I wanted! It works perfect! And you are right, you did not misunderstand my question! :)Iulus
Really great to see that. Enjoy the might UI-Router, sir ;¨)Edraedrea
B
0

There is another way how to solve the:

How to resolve $http request before the execution of the resolve property inside $stateProvider?

In case, that we just need to get some $http result inside of the resolve, we can do it just like this:

resolve: {
    myResolve1:
        function($http, $stateParams) {
            return $http.get("/api/foos/"+stateParams.fooID);
        }
    }

This is a snippet from documenation of the [$stateProvider][1], section resolve. We can see, that we return the promise of the $http service: return $http.get()

So, to extend that, to asnwer:

But how can I make the ui-router wait until the promise is resolved?

we can just use return $http.get() and then .then(). And inside of it, we have access to returned result - which we can adjust:

myResolve1:
    function($http, $stateParams) {
        // we still return the promise of the $http.get))
        return $http
          .get("/api/foos/"+stateParams.fooID)
          .then(function(response) {

            // but now, the content of resolved property
            //  will be the first item of the loaded array
            return response.data[0];
          };
     }
}

There is also enahnced solution - in case we need to make this to happen before every state. We just introduce some "root" state as a super parent. It will contain such resolve and all child states will wait until this one is resolved, but just resolved just once. See more here: angular ui-router resolve for parent state

Boiardo answered 21/3, 2015 at 17:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.