简体   繁体   中英

Why is child state resolve functions are executed before parent state promises are resolved

I'm using ui-router v0.2.13. This page states that:

All resolves on one state will be resolved before moving on to the next state, even if they aren't injected into that child

And more

All resolves for all the states being entered are triggered and resolvesd before the transition will enter any states (regardless of the resolve being injected somewhere)

However, in my case, child state resolve function is executed before parent's resolve promise is resolved. How is this possible?

Here:

$stateProvider
    .state('route1', {
        url: "/route1",
        templateUrl: "route1.html",
        resolve: {
            parent: ["$timeout", "$q", function ($timeout, $q) {
                var d = $q.defer();
                $timeout(function () {
                    d.resolve();
                }, 5000);
                return d.promise;
            }]
        }
    })
    .state('route1.list', {
        url: "/list",
        templateUrl: "route1.list.html",
        controller: function ($scope) {
            $scope.items = ["A", "List", "Of", "Items"];
        },
        resolve: {
            child: function () {
                alert("I'm shown before `parent` resolved");
            }
        }
    });

If you navigate to /route1/list the alert is immediately shown instead of waiting 5 seconds until a parent resolve promise is resolved.

All resolves are guaranteed to be resolved before transition is actually performed. But the statements don't point out that resolve functions will be called synchronously. And that's is correct.

According to the ui-router source code , invocables are resolved as "parallel" as possible. Only ones dependent on other invocables (either from parents or from current state declaration) will be executed after their dependencies are resolved.

So the only way to make child invocable to be invoked after parent is resolved is to specify parent as dependency of child invocable.

.state("route1",{
   //..
   resolve: {
        parent: ["$timeout", "$q", function ($timeout, $q) {
            var d = $q.defer();
            $timeout(function () {
                d.resolve();
            }, 5000);
            return d.promise;
        }]
    }
 })
 .state("route1.list",{
    //...
    resolve: {
         child: ["parent", function(parent) {
             //will be called only after parent is resolved
         }]
 })

Excerpt from the GitHub resolve.js source code comments:

Invocables are invoked eagerly as soon as all dependencies are available. This is true even for dependencies inherited from a parent call to $resolve .

So what is actually going on is that since you are not injecting "parent" into the child routes resolved, it will not wait for the parent before trying to resolve child. If you try this:

resolve: {
  child: function(parent) {
    alert("Child Resolved!");
  }
}

then your child's resolved will be called after the timeout (resolve) of the parent.

I believe what the doc means is that it will wait for all resolved before carrying out the actual transition to the state such as loading the controller and rendering the templates.

In the plunker below (which I mostly borrowed from an old scotch.io example) I played with moving around the dependencies. If the child resolve depends on a parent resolve then the parent promise is resolved before the child. If the child doesn't depend then the child resolves first (even if the timeout length is set to 0).

In all cases though, the parent and child controllers aren't entered, and the views aren't rendered until both the child and parent are both resolved.

http://plnkr.co/edit/EtU03AfgUAlWNsEH0TuU?p=preview

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM