Skip to content

How do you resolve a Controller before loading it's children? #104

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

Closed
mrrooijen opened this issue Apr 28, 2013 · 6 comments
Closed

How do you resolve a Controller before loading it's children? #104

mrrooijen opened this issue Apr 28, 2013 · 6 comments

Comments

@mrrooijen
Copy link

I have currently run in to a situation where I have an abstract state which has a controller that does a bunch of things. One among which performs an async call using ngResource to fetch some data from the server and assigns it to a property on $scope. However, since the state manager (from what I can see) doesn't wait until this asynchronous class is finished it goes ahead and loads the underlying states and controllers. Now the child controllers are loaded and they cannot find the $scope.thatResourceProperty because the async call isn't finished yet.

I tried to do a simple $q.defer(); $q.resolve(); $q.promise in the controller to see if the state manager works with promises and waits until it flags it as "resolved". It doesn't look like it's working, although I might be doing something wrong.

I also tried the resolve property on the .state function in $stateProvider but it looks like I can't inject all the stuff I need that I'm injecting in to my controller. Is there a way to tell the state manager to wait for the controller to resolve? Like

.state "accounts",
  abstract: true
  resolve: true
  controller: "Accounts"
  templateUrl: "/app/templates/accounts"

At the moment I'm having a hard time figuring out how to go about waiting for the controller and all it's asynchronous actions to finish before loading child (nested) states/controllers.

Any pointers much appreciated!

Cheers

EDIT: Ideally I would just want to use my controller and not a separate resolve property on the state manager as to me it feels like it's out of place. The logic I want should be in the controller and thus I want the controller to finish everything including it's async actions before proceeding down the hierarchy.

@jeme
Copy link
Contributor

jeme commented Apr 28, 2013

As a workaround for now, couldn't you assign a promise to $scope.thatResourceProperty and then use that?, meaning it would be assigned right away, but won't be resolved until your async task finishes.

AFAIK controllers are run orderly from top to bottom, but here I might actually be wrong.

Or does that break your templates somehow?... (I am currently expecting that your just trying to get around a x is undefined issue, that is as far as I understand your explanation at least).

Reason I suggest that as a workaround is that if we will provide some way of doing this, I imagine that it will be somewhat difficult to implement.

@mrrooijen
Copy link
Author

Ha! I just tried that (I never really used the $q utility so I'm pretty new to this concept) but I set up a simple deferred object on $scope.deferredResource without anything special, and just had it resolve() at the end of the success callback when everything was properly set on the $scope synchronously. Then in the "child" controller I added the $scope.deferredResource.promise.then -> and it properly waited until everything was returned before executing what it needed to wait for.

Thanks for the tip. Thus far it seems to work just fine! Will need to define a convention for this.

Reason I suggest that as a workaround is that if we will provide some way of doing this, I imagine that it will be somewhat difficult to implement.

My initial thought was that the state manager would check out the return value of the instantiated controller and if it's a promise then it'd wait for a resolve/reject. That said, it was just a thought and thus I'm unfamiliar with the internals of Angular / Angular UI Router so that might be an impossible or unviable construct.

The resolve property for the state function of $stateProvider I believe does accept a promise return value and waits for the resolve/reject? The problem I have with that approach is that it doesn't feel like you'd want to put anything complex like ngResource in there. Though, instead of passing in a function to resolve maybe you could pass a boolean true or string "controller" in to it to instruct it to await a promise from the assigned controller and wait with going down the state tree until a controller promise is resolved. Or, perhaps allow a controller to return an Array of promises in case there are multiple that need to be resolved? (Again, i dont know much about $q at this time, just throwing out ideas).

Again, thanks for the tip! This'll get me where I'm trying to go with my app.

Cheers

@jeme
Copy link
Contributor

jeme commented Apr 28, 2013

Values defined in resolve, is handled by the state provider, but the controller is handled by the ui-view directive, and by calling the $controller service in angular, so that is why I think it will be more complex to handle, if it even is possible if we wan't to keep things disconnected (low coupling). And we can't just leave it to the state provider to call the controller, as we don't have any scope there yet, and we can't get that because it wouldn't be placed correctly in the scope hierarchy then, so there is quite a few things that has to be thought trough and solved if we wan't to provide that.

But good to know you can get on with your project. I will leave it to @ksperling to add his thoughts here, before anything is considered about what we do and don't do.

@mrrooijen
Copy link
Author

Sounds good. Appears to be a lot more complex than I thought. That said, regardless of how it'd be implemented it'd definitely be great to allow controllers to resolve all async requests somehow before proceeding down the hierarchy of states/controllers to ensure certain values are definitely set.

For now I've been successful with your suggestion and will continue to use it. I'm not really bothered by this approach to be honest, it works well. If anything changes with controller resolving in the future I'll probably migrate over to whatever the "standard" has become.

Thanks again!

@ksperling
Copy link
Contributor

The disconnect between $state / state.resolve and controllers is somewhat bothersome, but as @jeme has explained is difficult to work around, because $state lives in 'service land' whereas controllers get instantiated as part of directive linking within the scope tree etc.

As an extreme case, if your template didn't have a ui-view tag corresponding to a controller/template you're setting up in a state, that controller would never get instantiated at all.

Support for state.resolve accessing resolved items from a parent state is on the TODO list, and I'm wondering if it's even feasible to allow dependencies between resolve items within a state itself, so you could do something like

resolve: {
  contacts: ...,
  selectedContact: function(contacts) {
    ...
  }
}

Not sure how common that sort of thing would be though. Another interesting feature might be the ability to flag state.resolve values so they get published on the scope automatically without having to have a controller purely to do a bunch of assignments to $scope.

In terms of a general pattern, at the moment there are two options for async loading of data:

  • using state.resolve, where everything will be ready before the transition and errors are propagated
  • using a controller to initiate the async process and assign a promise to the scope. errors need to be handled manually

Maybe we can come up with some guidelines for when which approach is appropriate.

@ksperling
Copy link
Contributor

Closing this as a duplicate of #73

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

3 participants