Repository navigation
Perhaps Router Resolve shouldn't wait for Observable to Complete? #10556
Description
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
[-]Perhaps Angular Router shouldn't wait for Observable to Complete?[/-][+]Perhaps Router Resolve shouldn't wait for Observable to Complete?[/+]johnchristopherjones commentedon Aug 17, 2016
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 orreturn Observable.of(this.myobservable);to return a hot observable?syndicatedshannon commentedon Aug 26, 2016
@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").
syndicatedshannon commentedon Aug 26, 2016
@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.
kemsky commentedon Sep 9, 2016
This is good idea, but i see at least two problems:
DzmitryShylovich commentedon Nov 6, 2016
I believe this was fixed #10412
syndicatedshannon commentedon Nov 7, 2016
Thank you for the find/reference, Dzmitry.
I raised a specific issue in my "desired behavior":
Is there a pattern in-mind, on how disconnects are handled now, after #10412 ?
syndicatedshannon commentedon Nov 7, 2016
@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.
swftvsn commentedon Oct 27, 2017
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.
51 remaining items
syndicatedshannon commentedon Oct 17, 2019
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.
syndicatedshannon commentedon Oct 17, 2019
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
Observableto aBehaviorSubjectin 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.
gabrieltbmaestro commentedon Oct 31, 2019
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.
atscott commentedon Jul 23, 2021
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
syndicatedshannon commentedon Jul 23, 2021
gjensen-gomeyra commentedon Jul 26, 2021
@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.
atscott commentedon Jul 26, 2021
@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.
greggbjensen commentedon Jul 26, 2021
@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.
syndicatedshannon commentedon Aug 22, 2021
@greggbjensen 's case is my main case as well. Similar to @greggbjensen , I've also already integrated a workaround for this.
@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.
angular-automatic-lock-bot commentedon Sep 22, 2021
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.