2

In my parent state I have a resolve. Currently, I do not inject the resolve key in any child state.

I assumed that my child state would not wait for this resolve/promise to be resolved. But when I set a timeout, I can see my child state does wait.

Is this correct behaviour?

.config(function ($stateProvider, $urlRouterProvider, STATES) {
    $stateProvider
        .state(STATES.ROOT, {
            abstract: true,
            template:'<div ui-view=""></div>',
            resolve: {                  
                UserService: 'UserService',
               userDetails: function(UserService){
                   return UserService.getUserProfile();
                }

            }
        })
        .state(STATES.BILLING, {
            url: '/bill/checkClientContext.html',
           templateUrl: 'bill/checkClientContext.html',
            controller: 'BillingController'
        })

UserService.js

'use strict';
angular.module('nabc.data')
    .service('UserService', ['AjaxService', '$timeout','$q', function(AjaxService, $timeout, $q) {
        var getUserProfile = function() {
            var promise = AjaxService.get('userProfile');

            var deferred = $q.defer();
            $timeout(function(response){
                deferred.resolve(response);
            }, 5000);
            return deferred.promise;


        };

        return {
            getUserProfile: getUserProfile
        };
    }]);

As you can see above the BillingController does not inject in userDetails. But when i set a timeout in the userService, i see that my billing state does wait.

shane lee
  • 1,290
  • 15
  • 17
  • You are right. I didn't know that the documentation says: "The resolve keys MUST be injected into the child states if you want to wait for the promises to be resolved before instantiating the children." (https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#inherited-resolved-dependencies). I always used it the other way around: hoping to get the parent's resolve promises resolved before instantiating any child state. – Augusto Barreto Feb 16 '15 at 03:41

2 Answers2

4

The answer could be found here (let me cite few lines and snippet):

Inherited Resolved Dependencies

New in version 0.2.0

Child states will inherit resolved dependencies from parent state(s), which they can overwrite. You can then inject resolved dependencies into the controllers and resolve functions of child states.

$stateProvider.state('parent', {
      resolve:{
         resA:  function(){
            return {'value': 'A'};
         }
      },
      controller: function($scope, resA){
          $scope.resA = resA.value;
      }
   })
   .state('parent.child', {
      resolve:{
         resB: function(resA){
            return {'value': resA.value + 'B'};
         }
      },
      controller: function($scope, resA, resB){
          $scope.resA2 = resA.value;
          $scope.resB = resB.value;
      }

So, as we can see in the doc, resolve on parent could be used on a child. And that is in fact reason, while we have to wait for this to be resolved... before we can continue.

But I would say, that this is expected. The more fancy feature would be:

if the child state has defined resolve - and parent does not - it would be great to see parent views rendered, and let only child's views to wait.

This is (as far as I know) planned in the near future as a feature...

EXTEND - await all to continue is the answer

To get the answer:

Must all the resolves - declared in current state and all its parent states - be awaited to continue with current? Even if current state controllers do not require any of these values?

Do observe the code: state.js

function resolveState(state, params, paramsAreFiltered, inherited, dst, options) {
  // Make a restricted $stateParams with only the parameters that apply to this state if
  // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
  // we also need $stateParams to be available for any $injector calls we make during the
  // dependency resolution process.
  var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
  var locals = { $stateParams: $stateParams };

  // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
  // We're also including $stateParams in this; that way the parameters are restricted
  // to the set that should be visible to the state, and are independent of when we update
  // the global $state and $stateParams values.
  dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
  var promises = [dst.resolve.then(function (globals) {
    dst.globals = globals;
  })];
  if (inherited) promises.push(inherited);

  // Resolve template and dependencies for all views.
  forEach(state.views, function (view, name) {
    var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
    injectables.$template = [ function () {
      return $view.load(name, { view: view, locals: locals, params: $stateParams, notify: options.notify }) || '';
    }];

    promises.push($resolve.resolve(injectables, locals, dst.resolve, state).then(function (result) {
      // References to the controller (only instantiated at link time)
      if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
        var injectLocals = angular.extend({}, injectables, locals);
        result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
      } else {
        result.$$controller = view.controller;
      }
      // Provide access to the state itself for internal use
      result.$$state = state;
      result.$$controllerAs = view.controllerAs;
      dst[name] = result;
    }));
  });

  // Wait for all the promises and then return the activation object
  return $q.all(promises).then(function (values) {
    return dst;
  });
}

I decided to cite the complete method here, but the most important parts are: declaration of the var promises = [];. This array is later extended with all resolves required to continue promises.push($resolve.resolve(... and finally, untill all the stuff is not done, no result - return $q.all(promises)

Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • Thanks for the reply. But that is not what i want. It states "The resolve keys MUST be injected into the child states if you want to wait for the promises to be resolved before instantiating the children." I have not injected in the userDetails yet to the billingController but it still waits. – shane lee Feb 16 '15 at 22:21
  • I think the wiki may be incorrect. My expectation is that all resolves for a transition will be resolved before the transition begins. – Chris T Feb 16 '15 at 23:26
  • Thats what I am thinking too. Just want confirmation!! – shane lee Feb 16 '15 at 23:54
  • I appended a snippet from the code, it should help. Now you must have your answer ;) – Radim Köhler Feb 17 '15 at 06:16
  • So are you stating that the documentation is incorrect and this is a bug? – shane lee Feb 19 '15 at 00:08
  • I think the documentation is missing some details. Resolve keys MUST be injected for child resolves to wait for parent resolves. But resolve keys are not necessary for child controllers. – Andrew C Mar 02 '15 at 16:39
1
  1. Parent controller always loaded (if not loaded before for some adjacent child state) before child controller.
  2. Controller need to wait for its resolve section.

Here is a short example, let's say we have parent state P with resolve section and two child states C1 and C2.

First case - you navigate to P.C1 state from separate state or just hit the corresponding URL in the browser. The order will be following:

  • Parent's resolve section executed
  • Parent's controller loaded
  • Child's controller loaded

Second case - you navigate from P.C1 to P.C2 in this case P don't need to be loaded, so only C2 controller is loaded.

udalmik
  • 7,838
  • 26
  • 40