Skip to content

Perhaps Router Resolve shouldn't wait for Observable to Complete? #10556

Closed

Description

@syndicatedshannon

This is probably best classified as a Router feature request. I'll describe what it is, my thinking, and a use case.

What it is: Currently, the V3 Router waits for an Observable to Complete before completing navigation. I would like for it to instead continue navigation after the first Observable item is returned.

My thinking: Currently the Resolve treats the Observable as a Promise, defeating the power of the Observable. This doesn't present a barrier with Angular's Http, where only one item is returned. But unless I misunderstand, the point of using an Observable in Http is to allow easy extension throughout Angular into a scenario where the data is actually a stream.

Use case: I have a live data feed from a remote server. I subscribe to a resource, rather than get it. When viewing this resource in a page, I only need one snapshot to complete navigation and display the component, but I would still like the updates bound to the view.

More detail:

Observables as first-class citizens in Angular2 are great for displaying "live data". I currently rely on, and it is becoming more common to integrate, "live data" streamed/syndicated/published from web services. Regardless of the framework used to deliver streams to the client, Observables are the obvious choice to act as intermediary between the receiving agent/service and the view.

Resolvers in Angular are also very nice for a few reasons. One is that I can be sure I have all my data before performing initialization. This greatly simplifies my component initialization logic. It also simplifies managing built-in operations Angular performs, such as avoiding errors binding templates to properties on null models.

But where live data comes in, the current Resolve behavior is difficult to make use of. If we suppose the live data stream never completes, we have to instead resolve, for example, to Observable.first(), and throw away the remainder of the stream.

Or I can go through extra steps for each of my Observables, to Observable.publish etc. But that's not enough, because there's not a symmetric exit hook for resolve, that I see. So instead I have to do some extra magic, such as tear down resources I didn't initialize in the component.

IMO, in the spirit of Rx, a better behavior would be to:

  • Share the Observable, e.g. via PublishReplay
  • Connect
  • Subscribe
  • Wait for the first item
  • Unsubscribe
  • Complete navigation
  • Pass the shared Observable as ResolvedData
  • When exiting navigation Disconnect

Or something similar.

Your thoughts are appreciated. I'll also be happy to contribute effort if someone with design authority confirms an approach.

Activity

  1. changed the title [-]Perhaps Angular Router shouldn't wait for Observable to Complete?[/-] [+]Perhaps Router Resolve shouldn't wait for Observable to Complete?[/+] on Aug 7, 2016
  2. added
    featureLabel used to distinguish feature request from other issues
    on Aug 9, 2016
  3. johnchristopherjones commented on Aug 17, 2016

    @johnchristopherjones

    I've actually been bashing my head against the wall for the last two days over this exact thing. I had no idea the observable had to complete in order for the navigation to complete. Retrospectively, I understand it where it came from—the promise based approach. Without live observables, it is proving to be difficult to merge the angular router with John Lindquist's course on building Redux-style Applications with Angular, RxJS, and ngrx/store.

    It's also pretty unexpected. For example, this guide on RXJS says “[c]ompletion should not be taken as "event at the end of the program" or something. It's an event which may happen or not.”

    Is the work-around something like return this.myobservable.take(1); from the DataResolver to return a cold observable or return Observable.of(this.myobservable); to return a hot observable?

  4. syndicatedshannon commented on Aug 26, 2016

    @syndicatedshannon
    Author

    @johnchristopherjones

    Yes, although I wasn't thinking about this as a defect-inducing issue because we've moved past that, I know what you mean, and we did initially experience that confusion as well. Several times our team hit a multi-hour snag trying to track this same issue down. The first was me, the next two were the same team member hitting it twice, once after a refactor. Embarrassingly, we both still stared at it for a while the second time: As usual, locating a defect with no visible error can be time-consuming.

    What is the workaround you are looking for? Yes, observable.take(1), a.k.a. observable.first() is how we initially hacked the issue. Unless I misunderstand your question, hot vs. cold not directly related.

    However, observable.first() will throw away all the following data. That's only a problem if you care about the following data (which you probably do, since otherwise your observable would probably be "completed").

  5. syndicatedshannon commented on Aug 26, 2016

    @syndicatedshannon
    Author

    @johnchristopherjones : Side note, I didn't see your question earlier. If you added it in an edit, you may want to post it separately next time.

  6. kemsky commented on Sep 9, 2016

    @kemsky

    This is good idea, but i see at least two problems:

    1. once router stops waiting for observable to complete we will loose possibility to do redirect before component is created. I would suggest to make Resolve configurable.
    2. we need to unsubscribe at some point and it gets tricky when components are reused by the router.
  7. DzmitryShylovich commented on Nov 6, 2016

    @DzmitryShylovich
    Contributor

    I believe this was fixed #10412

  8. syndicatedshannon commented on Nov 7, 2016

    @syndicatedshannon
    Author

    Thank you for the find/reference, Dzmitry.

    I raised a specific issue in my "desired behavior":

    • When exiting navigation Disconnect

    Is there a pattern in-mind, on how disconnects are handled now, after #10412 ?

  9. syndicatedshannon commented on Nov 7, 2016

    @syndicatedshannon
    Author

    @DzmitryShylovich After an errant discussion on #10412, it looks like although it is a related behavior, it has no effect on this issue.

    That PR is intended to change the behavior of guards (OnActivate), not Resolve.

  10. self-assigned this
    on Jun 5, 2017
  11. swftvsn commented on Oct 27, 2017

    @swftvsn

    Router 4 should really support hot routes, that is views that are updated once the observable emits new data.

    The activated route should flow the data and if route change is needed it should happen after the observable has emitted the first value.

    The disconnect to the resolve observable should happen when the route is not activated anymore.

  12. added this to the Backlog milestone on Jan 23, 2018
  13. 51 remaining items

  14. syndicatedshannon commented on Oct 17, 2019

    @syndicatedshannon
    Author

    Regarding '#4' on @Airblader 's summary above, it is related to but does not address this issue, which is primarily about Resolve. The statement would need to be rephrased to "remove observable support from guards and resolve".

    Guarding is an extra step, not about the readiness of data, even if they support Observables, but about tests on ready data. Personally, I've accepted that guards cannot 'fix-up' resolve because they are an 'if' question, not a 'when' question. I have tried to use them for such only because they seemed like an accessible development point.

    I can't say with certainty, why observables were chosen as the basis for resolve and yet they do not actually support data streaming, but it's likely the designer had in mind the ability to cancel network requests, which is obviously important. I appreciate that a promise appears to provide a more intuitive return type given the current operation, but only if we overlook cancellation.

    At a glance, as currently phrased, #4 is a non-starter.

  15. syndicatedshannon commented on Oct 17, 2019

    @syndicatedshannon
    Author

    IMO, an important aspect missing from that same summary is providing support for unsubscribing. For our projects, it may even be the most essential aspect, although it's hard to be certain since we've become accustomed to certain behaviors since this issue report 3 years ago.

    As many have suggested (on this thread and others), we can convert the Observable to a BehaviorSubject in a resolve handler. However, there is no unique ID associated with a route resolve action to track when that subscription has been expired, so we have to write our own route management handlers as well. That itself is challenging, because much of the code that implementation depends on is not public Angular API.

    Overall, I feel like, after RoutableComponents had such a rocky start, no one on the Angular team has been eager to take on these issues, which probably present not only design challenges but have historical baggage as well.

    Personally, if someone wants to as simply as possibly patch this issue for me and me only, I'd be satisfied with a symmetric resolve capability: knowledge of when a route enters and exits a particular requirement.

  16. gabrieltbmaestro commented on Oct 31, 2019

    @gabrieltbmaestro

    I've hit a bump in the road with this so far because some of my data comes from a websocket connection. What I thought I'd do is use a guard for authentication and authorization, followed by a resolver for this data, but as this thread confirms, nothing happens.

    How feasible is #5? Right now, my biggest issue is on manual URL changes which triggers an asymmetrical reloading of data across my components. Does anyone have a solution for this?
    Otherwise, have my vote for #5, fwiw.

  17. added
    feature: under considerationFeature request for which voting has completed and the request is now under consideration
    on Jun 4, 2021
  18. atscott commented on Jul 23, 2021

    @atscott
    Contributor

    The basic premise of resolvers is to block component instantiation until the items have been resolved. If there was no need to have the data before component instantiation, the loading logic should be moved to the component instead.

    For the case where new data needs to be resolved when something changes, this is somewhat available through the runGuardsAndResolvers option, where the resolver data could be updated when parameters change.

    Other use-cases may generally be covered by #42953 if the router were more extensible and allowed for more configuration as to how guards and resolvers are run.

    Closing in favor of #42953

  19. syndicatedshannon commented on Jul 23, 2021

    @syndicatedshannon
    Author
  20. gjensen-gomeyra commented on Jul 26, 2021

    @gjensen-gomeyra

    @atscott I do agree the premise of resolvers is to have the data before component instantiation. This is still true in my example. However, there are many cases where I want to receive additional updates to the data my components can respond to. This is what I use the HotResolver for. It verifies initial data was loaded and I am able to continue to listen to that data for changes. This still seems like a very valid use of a resolver to me. Initial data should be required, but that does not mean the observable has to be completed.

    In our case, the data updates are coming through Web Sockets, and the URL will never change for the resolve. Maybe the change here would be to simply use first() on a resolver instead of requiring a completed observable. #42953 does not seem to address this particular use case of resolvers.

  21. atscott commented on Jul 26, 2021

    @atscott
    Contributor

    @gjensen-gomeyra The intent behind #42953 would be that you would have a way to completely rewrite how resolvers are handled. So rather than having to fork the entire router package, you could for just the resolver code and swap the resolve stage out to your own implementation. It's not necessarily that we would implement the feature into the router for you, but rather give developers the option to define this behavior themselves. This would be a much more sustainable model moving forward rather than trying to bake in each particular use-case.

  22. greggbjensen commented on Jul 26, 2021

    @greggbjensen

    @atscott I do have it working using a wrapper, as shown above. I just thought it would be useful to other users. I can also create a library that developers can add to their projects. Maybe that would be the right route to go.

  23. syndicatedshannon commented on Aug 22, 2021

    @syndicatedshannon
    Author

    @greggbjensen 's case is my main case as well. Similar to @greggbjensen , I've also already integrated a workaround for this.

    The basic premise of resolvers is to block component instantiation until the items have been resolved. If there was no need to have the data before component instantiation, the loading logic should be moved to the component instead.

    @atscott 's closing comment didn't demonstrate understanding of this specific issue/thread, but the follow-up to @greggbjensen on July 26 did. Thank both of you for that.

    I do appreciate that Observable was chosen for HTTP calls because of the ability to chain cancellations and similar, and router today basically expects it to behave as a cancellable promise. Therefore, I do see how this can be considered "as-designed".

    It's very confusing in the current architecture to route hot subscription data into components, and it seems like a common feature to provide. That is the main thrust of this issue. I hope #42953 allows for that, whatever its design entails.

  24. angular-automatic-lock-bot commented on Sep 22, 2021

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  25. locked and limited conversation to collaborators on Sep 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions