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

Resolve promise each time before controller is executed #2604

Closed
slavafomin opened this issue Mar 4, 2016 · 11 comments
Closed

Resolve promise each time before controller is executed #2604

slavafomin opened this issue Mar 4, 2016 · 11 comments

Comments

@slavafomin
Copy link

Hello!

Thank you for this great module and your awesome work!

However, I'm looking for a way to run my asynchronous function before the controller is executed for some state. I want this to happen every time when client is transitioned to the state.

I've tried to use resolve object in a state configuration, however, the resolves will not be re-evaluated when navigating from child to parent state and I don't want to call reload manually or provide it as an argument somewhere.

Also, I know that I can prevent navigation in the $stateChangeStart event and then manually continue it, but it will reject the original promise, which I don't want.

Are there any real alternatives? I've spent almost two days trying to figure out this pretty simple usage scenario.


Actually, I'm implementing an extension module for UI Router that will support conditional stylesheets. Developer will be able to define the list of named resources as a part of state configuration and they whould be loaded when the state is activated, before the view is rendered and controller is executed. I will gladly accept any suggestions!


Or probably there is a function that determines whether the state needs to be reloaded. Is there a way to decorate/overload it? That way I will be able to compare required resources between states and make a decision.


Right now I'm using $state service decorator as a temporary workaround:

// Forcing state reloading in order to always trigger resolve re-evaluation.
$provide.decorator('$state', function ($delegate) {
  var originalTransitionTo = $delegate.transitionTo;
  $delegate.transitionTo = function () {
    var optionsIndex = 2;
    arguments[optionsIndex] = angular.extend({
      reload: true
    }, arguments[optionsIndex]);
    return originalTransitionTo.apply(originalTransitionTo, arguments);
  };
  return $delegate;
});

But I'm not really sure if it's safe to use such approach.

Thank you!

@christopherthielen
Copy link
Contributor

As you probably know, neither resolves nor controllers are reloaded for "retained" states. When navigating from child to child, or child to parent, the parent state is retained; it is neither entered nor exited.

If you're writing a third party extension, I do not recommend forcing other people's apps to { reload: true } as this may not be what they want.

The 1.0 transition rewrite addresses your use case. Transition hooks can pause the transition while waiting for some promise. The hooks can be added to transitions in a flexible, declarative way. However, the "retained" controllers will still not be reloaded.

@slavafomin
Copy link
Author

Thank you @christopherthielen for elaborated answer! It's a shame that UI Router doesn't provide any hooks to execute custom code before any transition while pausing it at the same time in current incarnation. That makes my life extremely difficult, because it's a great place to implement authentication/authorization, state customization and such.

Are there any estimates on release date for 1.0 by the way? And from your experience/vision, is it stable enough to be used in production right now, or should we stick to the stable version for now?

Also, is there an API docs for 1.0?

Thank you very much for your hard work! It's highly appreciated!

@christopherthielen
Copy link
Contributor

Read over our release notes and the list of breaking changes #2219

The docs are currently at http://angular-ui.github.io/ui-router/feature-1.0/ but are very much a work in progress. See http://angular-ui.github.io/ui-router/feature-1.0/classes/transition.hookregistry.html for the transition hook documentation.

I think alpha0 is pretty solid and will likely work fine for your app in production. There will be some bugs in there, which may or may not affect your app. Some of the new APIs will be tweaked before final release.

@slavafomin
Copy link
Author

Thanks! That's pretty cool piece of software you've got there. I think it will solve a lot of concerns. Waiting forward to see it in production! Godspeed!

@eddiemonge
Copy link
Contributor

closing due to inactivity

@chillyistkult
Copy link

I am wondering why few people actually take part of the discussion, because the OP's usecase seems pretty common. I deal with the very same problem and my only workaround for now was to define a stateDecorator that force reloads the state so that I can be sure even if I visit a child state my resolved data is up to date.

That's not a good solution as you mentioned because I dont want to re-initialize every parent controller when I visit a child state.

What is the best solution for ensuring that only the resolves are up to date down the state tree?

@christopherthielen
Copy link
Contributor

@chillyistkult

Tell me exactly what you want to do. It sounds like you want some object defined at a parent state, injected into children states, then have that object dynamically update whenever the user activates any substate. Is that correct?


This is the typical use case that resolve is used for:

  • a state has a parameter.
  • that parameter is used to fetch a specific resolve value
  • the transition blocks while the resolve is being fetched
  • that resolved value is available (injectable) to that state and all the children, as long as the state remains active
  • if the parameter value changes, the resolve is re-fetched. the state and all the children states are exited and re-entered. They inject the new resolve.

There's no baked-in mechanism for modifying a resolved value without reloading the state. Reloading the state ensures that 1) the resolve is fetched and 2) the resolve can be injected into a child (it's a different object reference, after all).

If you want to dynamically update an object reference, you'll have to manage that state yourself.

This sounds like it might be a good use case for Observables.

@chillyistkult
Copy link

chillyistkult commented Jun 27, 2016

Hello @christopherthielen, your assumption is on point. We use resolves to fetch data from our backend. This requests don't have to be necessarily bound to a route parameter.

For example we have a user object that we need on almost all our states, so we defined a resolve on the root state so it is avaiable on all our child states. Of course the user can update his profile on various pages and the data gets saved in the backend.

Now we have exactly the problem as you described: We have to reload our root state to get this user object updated (we have datastores, so it's not another request to the backend in case you wonder). But anyway we have to re-resolve it and reloading the whole state tree is not an option.

We have events available for all our datastores. So if an object is updated in the datastore an event is triggered and for example a controller can listen on this event and update the data. The problem here is that we have a lot of data and with this approach we have to define the listeners in all our controllers which causes a lot of boilerplate.

I know that problem might not be related to ui-router as it is more of a general dataflow architecture question.

What we like about resolves:

  • They get resolved before the view is rendered, which is great because you can apply some kind of loading overlay without having cluttered DOM in background.
  • You can define them on a root state and inject the resolved value in every child. That means we can - in theory - declare our data access code in one single place.

What we don't like:

  • What happens if data updates and you navigate between childs but the resolved value is now outdated? Then it's over with the cool concept of declaring data access functionality in one single place, because you have to check in every single controller if data is up to date AND on top the resolved value is still not up to date till you reload that root state.

@christopherthielen
Copy link
Contributor

What happens if data updates and you navigate between childs but the resolved value is now outdated?

@chillyistkult Do you want to inject the resolved value into the newly activated CHILD or some PARENT? If you only care to inject it into a child, this is achievable using 1.0. However, you can't change the resolve value injected into a parent, for (possibly obvious) reasons.

In 1.0 you can add a resolve to any transition on the fly. The resolved data is available to be injected in any newly entered states and their components.

I think you might really benefit from using Observables though. You can resolve to an Observable (once, in the root state), and that single Observable can stream values, as they change.

@chillyistkult
Copy link

chillyistkult commented Sep 23, 2016

For me it would have been important to inject AND update the resolved value down the state tree (parent and it's childs), to ensure that all related controllers are getting up to date data. In 1.0 this would be solvable but not in a way that it's feasible on big projects. So we shifted away from the idea to "abuse" the state resolve functionality as a system to update and deliver data across the app (however I somehow liked the idea it seemed simple and intuitive).

We recently implemented event driven datastores (observable) as you suggested.

@christopherthielen
Copy link
Contributor

christopherthielen commented Sep 23, 2016

For me it would have been important to inject AND update the resolved value down the state tree (parent and it's childs), to ensure that all related controllers are getting up to date data.

Understood. Resolving the data directly in the parent state definitely isn't the right solution for that. I think your event driven datastores (possibly even exposed to states as a resolve) seems like an appropriate solution here.

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

4 participants