getn_outchea
getn_outchea

Reputation: 110

AngularJS UI Router not executing unspecified resolve dependencies in order

I'm currently running in to a problem with how UI router handles the order it loads its resolves. The end goal of what I'm trying to accomplish is having services that load their data and store it, and have that data be loaded (requested) only once and then accessible through the service.

The current way I've gone about implementing it follows this pattern:

In a higher level state resolve, tell the service to load its data. The result of the resolve is not a dependency of sub services, because the data is accessed by injecting the service in the controller. Sub state resolves may depend on the service that has been "primed" in a previous state This works great when you're normally clicking through the application (i.e, state a -> state a.b -> state a.b.c) however, it breaks down when you go directly to a nested state (state a.b or state a.b.c). The problem occurs because UI router will not ensure the order resolves are executed in if a child state does not specify a resolve of a parent state as a dependency. See the example below:

app.config(function($stateProvider) {

  $stateProvider
  .state('a', {
    url: "/a",
    resolve: {
      data: function(service) {
        return service.getData(); // makes network request
      }
    }
  })
  .state('a.b', {
    url :"/b",
    resolve: {
      specificData: function(service) { // if you add data as a dependency here, it works fine
        return service.useDataToGetSpecificData(); // doesn't make network request
      }
    }
  })

});

In the example, if a user goes directly to state a.b the specificData resolve will fail. service.useDataToGetSpecificData makes no network requests and accesses the data currently stored in service. This call fails because the resolve of state a has not finished yet because it is a network request.

A solution would be to have the service handle the case where it doesn't have data yet and manage that by waiting until it has data, but that feels a bit overblown for what a service would be doing, and seems to defeat the point of UI router.

Another solution (what I'm currently going with) is adding "data" to as a dependency of the resolve in state a.b even though it isn't used. This forces a.b to wait on a to finish its resolves, and everything works great. However, this feels like a hack and is extremely non intuitive. Anyone looking at this code without knowing would scratch their head as to why this unused variable was declared in a.b's resolve.

The order of resolves not being guaranteed without specifying a dependency is the way UI Router is written to behave, so I feel like I must be doing something weird / wrong.

Upvotes: 0

Views: 385

Answers (2)

Norbor Illig
Norbor Illig

Reputation: 658

I have a similar scenario and it works for me just like you want to. My config looks like this(taking you config as base):

app.config(function ($stateProvider) {
   $stateProvider
   .state('root', {
      abstract: true,
      resolve: {
         data: function (service) {
            return service.getData(); // makes network request
         }
      }
   })
   .state('a', {
      url: "/a",
      parent:'root',
      resolve: {
         data: function (data) {
            return data; 
         }
      }
   })
   .state('b', {
      url: "/b",
      parent:'a',
      resolve: {
         specificData: function (service) { 
            return service.useDataToGetSpecificData(); 
         }
      }
   })
});

The difference in my case is the abstract 'root' state, where I need to load some global data regardless of the url requested. Could you try that approach?

Upvotes: 1

Cyril CHAPON
Cyril CHAPON

Reputation: 3747

Another solution (what I'm currently going with) is adding "data" to as a dependency of the resolve in state a.b even though it isn't used. This forces a.b to wait on a to finish its resolves, and everything works great. However, this feels like a hack and is extremely non intuitive

This is the common way to do this. I usually do this to add multiple sequential resolves to one route. For example, I often add an attachUser hook that returns a promise from HTTP GET request, then another isLoggedIn that has attachUser as a dependency. I've searched a lot and never found better. Nothing special was writen for this case in ui-router, beyond angular's injector. Even if your case is special because of inheritance, this is exactly the same concern as here and here.

so I feel like I must be doing something weird / wrong.

You're not. It's most likely thought weird / wrong

Upvotes: 1

Related Questions