Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Child States are not waiting for parent state promise resolves #1903

Closed
samithaf opened this issue Apr 21, 2015 · 27 comments
Closed

Child States are not waiting for parent state promise resolves #1903

samithaf opened this issue Apr 21, 2015 · 27 comments

Comments

@samithaf
Copy link

Hi Guys,
According to the documentation child states should be able to access parent state resolved promises. In the documentation, https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#inherited-resolved-dependencies "To provide resolved dependencies via resolve for use by child states." However it seems like when I check the property exists on state change start method, parent state promise has not been resolved.

I have created a small app based on http://scotch.io sample application. As you can see in authorize method I am checking user object exists in child state. I am setting the property in parent state. But property has not been set. Please see the sample app in http://plnkr.co/edit/hS8jKONk0ZfyJfE4r7Ty?p=preview

@eddiemonge
Copy link
Contributor

The child states need the parent state as a dependency in order for it to wait to load, even if its not used directly.

@nateabele
Copy link
Contributor

Perhaps this belongs in the FAQ?

@BrianMcBrayer
Copy link

Can you give an example of how a child state includes a parent state as a dependency? I cannot inject the parent state or the parent state's resolver into the child state.

@eddiemonge
Copy link
Contributor

angular.module 'example'.config ( $stateProvider ) ->
  $stateProvider.state 'parent',
    resolve: User: ( UserModel ) ->
      UserModel.query().$promise

...

  $stateProvider.state 'parent.child',
    resolve: Profile: ( User ) ->
      Profile.get( User.id ).$promise

@zhdanovartur
Copy link

+1

@BrianMcBrayer
Copy link

+1. Thanks so much @eddiemonge

@bmkrocks1
Copy link

+1. I'm having the same issue.

@eddiemonge
Copy link
Contributor

@bmkrocks1 which is what? Did you do like I showed in the example?

@aligajani
Copy link

@eddiemonge , I am having a problem which is similar to this. My child state shows a complete blank page, except the navigation bar, when it is refreshed via the browser. My parent state uses a $http service to get data from the backend, and I think the child state is executing before the parent state is somehow resolved. If do it step by steps via ui-srefs, it works absolutely fine.

@mikrich
Copy link

mikrich commented Nov 10, 2015

+1

@geori
Copy link

geori commented Dec 3, 2015

We are having this same issue with our app. The app was working with the solution @eddiemonge mentioned above. I wrote it off as a problem due to Angular 1.5 Beta, but it could be because of a newer version of ui-router (we are on 0.2.15)

@eddiemonge
Copy link
Contributor

without code or examples its hard to troubleshoot the problems everyone else is having

@geori
Copy link

geori commented Dec 3, 2015

Fortunately for ui-router, I got some code review on my app. There had been a change to the promise chain and this caused the error. I was not returning the invocation of a factory function. See:

// parent
AppController.resolve = {
  authUser: ['$state', 'AuthFactory', 'BookmarksFactory'
    function ($state, AuthFactory, BookmarksFactory) {
      return AuthFactory.isAuthenticated()
        .then( function(){ BookmarksFactory.load(); });
  }]
};

// child
ConversationsController.resolve = {
  default: ['$q', 'authUser',
    function ($q, authUser) {
      console.log('resolving new conversation');
      console.log(authUser);
      return $q.when();
    }
  ]
};

The problem was occurring because i did not have a return in front of the BookmarksFactory.load() promise:
.then( function(){ return BookmarksFactory.load(); });

@adrianlungu
Copy link

I'm also having an issue with this with the following case:

.state('admin', {
                abstract: true,
                url: "/admin",
                templateUrl: "admin.html",
                resolve: {
                    deps: ['$ocLazyLoad', function ($ocLazyLoad) {
                        return $ocLazyLoad.load({
                            serie: true,
                            files: [
                                'bower_components/ng-tags-input/ng-tags-input.min.css',
                                'bower_components/ng-tags-input/ng-tags-input.bootstrap.min.css',
                                'bower_components/ng-tags-input/ng-tags-input.min.js',
                            ]
                        });
                    }],
                }
            })
.state('admin.super', {
                url: "",
                templateUrl: "super.html",
                controller: "adminSuperController",
                controllerAs: "vm",
                resolve: {
                    deps: ['$ocLazyLoad', function ($ocLazyLoad) {
                        return $ocLazyLoad.load({
                            serie: true,
                            files: [
                                'admin/super/admins.js',
                                'admin/super/service.js',
                            ]
                        });
                    }],
                }
            })

And I get the following error:

Error: [$injector:nomod] Module 'ngTagsInput' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument. http://errors.angularjs.org/1.4.9/$injector/nomod?p0=ngTagsInput at http://test.takeme.xyz:8888/voxbox/app/bower_components/angular/angular.js:68:12 at http://test.takeme.xyz:8888/voxbox/app/bower_components/angular/angular.js:2006:17 at ensure (http://test.takeme.xyz:8888/voxbox/app/bower_components/angular/angular.js:1930:38) at module (http://test.takeme.xyz:8888/voxbox/app/bower_components/angular/angular.js:2004:14) at L (http://test.takeme.xyz:8888/voxbox/app/bower_components/oclazyload/dist/ocLazyLoad.min.js:8:460) at http://test.takeme.xyz:8888/voxbox/app/bower_components/oclazyload/dist/ocLazyLoad.min.js:8:7109 at processQueue (http://test.takeme.xyz:8888/voxbox/app/bower_components/angular/angular.js:14991:28) at http://test.takeme.xyz:8888/voxbox/app/bower_components/angular/angular.js:15007:27 at Scope.$eval (http://test.takeme.xyz:8888/voxbox/app/bower_components/angular/angular.js:16251:28) at Scope.$digest (http://test.takeme.xyz:8888/voxbox/app/bower_components/angular/angular.js:16069:31)

Now, I'm not sure if I'm misinterpreting the documentation but from what I've read, the child inherits the resolve from the parent, but in this case, it seems the child is resolving before the parent and thus not being able to display the page because it can't find the dependencies in the parent.

@eddiemonge
Copy link
Contributor

Like I said in my first comment, you have to require the parent in the child.

@adrianlungu
Copy link

Oh, don't know how I missed that, it's working now, thanks!

@deleugpn
Copy link

@adrianlungu how did you solve your problem? I can't understand anything happening in the snippet provided by eddiemonge, but your code is a lot more familiar and your solution could help me.

Thanks.

@adrianlungu
Copy link

adrianlungu commented Aug 19, 2016

@deleugpn this is the code I ended up with:

.state('admin', {
                abstract: true,
                templateUrl: 'admin/index.html',
                url: "/admin",
                resolve: {
                    depsAdmin: ['$ocLazyLoad', function ($ocLazyLoad) {
                        return $ocLazyLoad.load({
                            name: 'app.admin.core',
                            serie: true,
                            files: [
                                'admin/core/core.module.js',
                                'admin/core/core.service.js',
                            ]
                        });
                    }]
                }
            })
.state('admin.super', {
                abstract: true,
                url: "/super",
                templateUrl: "admin/super/index.html",
                data: {
                    permissions: {
                        only: ['superAdmin'],
                        redirectTo: 'admin.login'
                    }
                },
                resolve: {
                    depsAdminSuper: ['$ocLazyLoad', 'depsAdmin', function ($ocLazyLoad, depsAdmin) {
                        return $ocLazyLoad.load({
                            name: 'app.admin.super',
                            serie: true,
                            files: [
                                'admin/super/super.module.js',
                                'admin/super/header.directive.js',
                            ]
                        });
                    }],
                    $title: function () {
                        return 'Super Administration';
                    }
                }
            })

The difference is very subtle, but basically it all comes down to these 2 lines:
depsAdmin: ['$ocLazyLoad', function ($ocLazyLoad) {
and
depsAdminSuper: ['$ocLazyLoad', 'depsAdmin', function ($ocLazyLoad, depsAdmin) {

Basically, named the parent dependency array "depsAdmin" and gave it as a parameter to the child dependency "depsAdminSuper".

Hope that helps!

@abrahamD
Copy link

Is there a reason why the child state does not wait for the parent's resolves? It seems to me this should be the default behaviour.

@amalitsky
Copy link

amalitsky commented Dec 1, 2016

Seems like it works as expected in v.1.

The child states need the parent state as a dependency in order for it to wait to load, even if its not used directly.

Should have been a part of documentation for 0.2.

@tanasebutcaru
Copy link

tanasebutcaru commented Dec 15, 2016

Thanks @amalitsky, it worked!

'parent':
resolve: 
    parentData: ...

'parent.child':
resolve:
   childData: ['parentData', ...

Using ui-router 0.2.15.

@christopherthielen
Copy link
Contributor

christopherthielen commented Dec 15, 2016

Is there a reason why the child state does not wait for the parent's resolves? It seems to me this should be the default behaviour.

@abrahamD

0.x legacy behavior

In 0.x, all resolves are "eager". If you transition from state foo to parent.child.grandchild, the resolves for parent, parent.child, and parent.grandchild will start loading eagerly. As soon as all of a resolve's dependencies are ready, the resolve will load.

If there is an implicit dependency between parent and child resolves, the child resolve should inject the parent resolve, so it will wait for the parent to complete before the child starts.

1.0 behavior

In 1.0, we added different resolve policies of LAZY and EAGER.

In 1.0, resolves by default are "LAZY". A state's LAZY resolves will wait for the parent state's resolves to finish before beginning to load (in reality, LAZY resolves begin when the state is being entered)

We made this change due to feedback like yours. Each state's resolves waiting for the parent states' seems the least surprising. If anyone wants "EAGER" behavior they can opt in.

@abrahamD
Copy link

It's good to see a project evolve with user feedback. Thank you for the response @christopherthielen.

@matt-webfab
Copy link

Been chasing my tail on this, and I'm glad to find an explanation (lazy/eager), but the response by @christopherthielen leaves me a bit confused.

He says for 0.x, "If there is an implicit dependency between parent and child resolves, the child resolve should inject the parent resolve, so it will wait for the parent to complete before the child starts." It seems that if a state is a child of another, that in itself is implicit, and that explicit would be if I specifically typed in things to inject.

Also it took me a bit to figure out what was meant by eager and lazy, which I now understand to mean async (just run them and I don't care what order they come back or when) and sync (chained in order as proper dependencies and don't do anything else till they're done). The html script tag "async" attribute behaves this way.

Semantics aside, using 0.x requires me to add a dummy resolver with parent dependencies for every child state, which is not "dry", and exactly what I was trying to avoid by using child states to begin with ("globally" waiting for firebase to initialize and checking login status before deciding if a user has privileges to go where they were trying to, in both "hot" and "cold" situations). I'm pasting these dummies on everything and getting odd results. Switching versions mid-project isn't an option. Is there a magic config I can toggle?

@brphelps
Copy link

No magic config, although my team is creating something which is "magically" wrapping child states with this logic to emulate what we think should be expected behavior.

@christopherthielen
Copy link
Contributor

christopherthielen commented Mar 21, 2017

@matt-webfab

Also it took me a bit to figure out what was meant by eager and lazy

to clarify,

using 0.x requires me to add a dummy resolver with parent dependencies for every child state, which is not "dry", and exactly what I was trying to avoid by using child states to begin with

No, in 0.x all the resolves are processed first (eagerly). When they are done, then states are entered. in 0.x, resolves are processed as soon as possible (eagerly), when all of the resolve's dependencies are ready.

Adding a dummy resolver with parent dependencies to a state has no effect on the state being entered. However, adding dummy parent dependencies to a resolve (such as a firebase request which requires auth) would have an effect.

In 0.x, the only way to implement what you are attempting is with a state change start event (cancel the transition, ensure the user is authenticated, then re-run the transition), or via deferIntercept to when the application is first started. The problems with doing these things is precisely why ui-router 1.0 abandoned the state change events in favor of Transition Hooks, which allow you to implement your use case easily.

@nermand
Copy link

nermand commented Mar 28, 2017

If you have multiple promises being resolved in the child state, only those depending on the parent's promise will wait for it to be resolved.
So, it's not like all resolutions wait for the parent, rather they are run as async, except the one actually being injected with parent's promise.

I had problems making it work, so I hope this comment will save time for someone else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests