NavigationExtras when returning URLTree from guard #27148
Comments
|
Actually, if going down this route, we'd probably need to split |
|
@Airblader This is a good feature request and this was discussed when working on the API for this redirect feature. The question is, what is the use case? What if I don't mind adding something like this, I just want to make sure the use cases are clearly defined. If it's something like Thanks for the issue! |
|
@jasonaden Thanks for the reply! Our concrete usecase is e.g. a 403 redirect where we want to show an error page but retain the URL. So no, the forwarding of the navigation extras wouldn't help us here, unfortunately. |
|
I had a whole comment ready to go to post on the primary issue (#24618) assuming this was a bug until I ran into this issue: in my opinion I would say the default use case should be for it to consider this a redirectTo and Here are the main scenarios I'm returning
In all of these examples I wouldn't want the original url to exist as valid locations in the browser |
|
@jayoungers I think in your second example replaceUrl would also be a reasonable choice, so I think just setting skipLocationChange doesn't cut it here. Also that would be pretty unexpected and confusing in my opinion. |
|
@Airblader I think replaceUrl would probably work in all of the scenarios. When the
Regarding the scenario: is the route replacement the unexpected/confusing part? or just the fact the url is altered? My scenario probably isn't the best representation, but if the issue is around the route being altered/standardized in general, I think that's a pretty common practice (news sites usually rewrite the url with the article title, stackoverflow will do this as well if the url only contains the question id, etc) |
The unexpected part is that the navigation works differently than any other navigation where I don't set any navigation extras. IMHO here Angular would suddenly dictate opinion over what navigation does or doesn't make sense, and I would like to avoid that. Everywhere else the user gets to choose the options, so it should be the same here. Failing that, it should at least behave the same way as everywhere else (which is the current behavior). |
|
I see. I think the answer comes down to what the intention of returning I'm looking at is as though the requested route is not valid under any circumstance. If we're looking at it as a short hand for the navigate method, then what you're saying makes perfect sense. In my mind I would have had the functionality setup so that if In scenarios such as authentication for example, where the user isn't logged in or the session has expired, I don't think |
|
But the benefit and entire point of allowing returning a tree is that it makes how multiple guards interact predictable and stable. This feature wasn't about specific usecases. I'd like to start returning URL tree in as many cases as I can (whenever I don't return true). |
|
I agree: based on the original requirements I would say I'm definitely not looking at the feature as it was intended. If you don't mind, just out of curiosity, would you happen to have an example scenario of multiple guards firing on a route that result in different |
|
I don't have a concrete usecase for this from my own projects, but admittedly I don't use anywhere near the full feature set of the router. I think with its extensive support of multiple outlets and the like such scenarios are very likely, for example authenticating on different resources or something. |
|
Two concrete use cases I have throughout my project in 1) Redirecting to a 404 page when trying to access an unknown resource
2) Performing a 301 redirect to a resource's canonical URL
|
@Airblader good point. the current code is using a serialized url to redirecting, see the source. @jasonaden Concrete use cases are not the major concern for the feature request. There are so many possibilities.
Since for the first glance, you don't have valid point about what will be bad for the framework. I believe we could move on to make this happen. |
|
@Airblader @jayoungers @scott-ho @axelboc Thanks for all the comments on this issue. I want to get this resolved somehow in the very near future. Ideally in the I'm happy to see the We were looking at an issue internally and found that, with the redirects being done the way they currently are, the I would love some feedback from anyone using this feature or wanting to use this feature. We can get this implemented quickly and pushed out as part of That being said, the original design did contain an option to provide |
|
@jasonaden - If I'm following the proposed With just that fix, I believe that should resolve all of my scenarios, and the 2nd example @axelboc provided. I think the 1st example would need the additional functionality |
|
@jasonaden the v8 fix you're suggesting sounds great. You're right, it would at least fix the back button for now. |
|
I was confused that this wasn't implemented because of the function createUrlTree on the router, which has the NavigationExtras as a parameter. I misunderstood what that function did. Just to add my own case, I had a CanActivate guard verifying that the user is part of a certain group before accessing a page. If he isn't, then redirect to an AccessDenied page that can display the group which the user lacks. The group name would've been passed to the AccessDenied page via NavigationExtras' state within the guard, since the guard is better positioned to know the group name, and the AccessDenied component may potentially be used in different contexts, by different guards. Hope that makes sense. |
As this been fixed? We've just discovered that returning a |
Without this change when using UrlTree redirects in `urlUpdateStrategy="eager"`, the URL would get updated to the target location, then redirected. This resulted in having an additional entry in the `history` and thus the `back` button would be broken (going back would land on the URL causing a new redirect). Additionally, there was a bug where the redirect, even without `urlUpdateStrategy="eager"`, could create a history with too many entries. This was due to kicking off a new navigation within the navigation cancelling logic. With this PR the new navigation is pushed to the next tick with a `setTimeout`, allowing the page being redirected from to be cancelled before starting a new navigation. Related to angular#27148
|
My apologies on this. It should have been in 8, but got missed. See above for the fix. This should land shortly. |
Without this change when using UrlTree redirects in `urlUpdateStrategy="eager"`, the URL would get updated to the target location, then redirected. This resulted in having an additional entry in the `history` and thus the `back` button would be broken (going back would land on the URL causing a new redirect). Additionally, there was a bug where the redirect, even without `urlUpdateStrategy="eager"`, could create a history with too many entries. This was due to kicking off a new navigation within the navigation cancelling logic. With this PR the new navigation is pushed to the next tick with a `setTimeout`, allowing the page being redirected from to be cancelled before starting a new navigation. Related to angular#27148
Without this change when using UrlTree redirects in `urlUpdateStrategy="eager"`, the URL would get updated to the target location, then redirected. This resulted in having an additional entry in the `history` and thus the `back` button would be broken (going back would land on the URL causing a new redirect). Additionally, there was a bug where the redirect, even without `urlUpdateStrategy="eager"`, could create a history with too many entries. This was due to kicking off a new navigation within the navigation cancelling logic. With this PR the new navigation is pushed to the next tick with a `setTimeout`, allowing the page being redirected from to be cancelled before starting a new navigation. Related to angular#27148
#31168) Without this change when using UrlTree redirects in `urlUpdateStrategy="eager"`, the URL would get updated to the target location, then redirected. This resulted in having an additional entry in the `history` and thus the `back` button would be broken (going back would land on the URL causing a new redirect). Additionally, there was a bug where the redirect, even without `urlUpdateStrategy="eager"`, could create a history with too many entries. This was due to kicking off a new navigation within the navigation cancelling logic. With this PR the new navigation is pushed to the next tick with a `setTimeout`, allowing the page being redirected from to be cancelled before starting a new navigation. Related to #27148 PR Close #31168
#31168) Without this change when using UrlTree redirects in `urlUpdateStrategy="eager"`, the URL would get updated to the target location, then redirected. This resulted in having an additional entry in the `history` and thus the `back` button would be broken (going back would land on the URL causing a new redirect). Additionally, there was a bug where the redirect, even without `urlUpdateStrategy="eager"`, could create a history with too many entries. This was due to kicking off a new navigation within the navigation cancelling logic. With this PR the new navigation is pushed to the next tick with a `setTimeout`, allowing the page being redirected from to be cancelled before starting a new navigation. Related to #27148 PR Close #31168
#32988) Resubmit #31168 now that google3 tests can pass. This requires http://cl/272696717 to be patched. Original description from jasonaden: Without this change when using UrlTree redirects in urlUpdateStrategy="eager", the URL would get updated to the target location, then redirected. This resulted in having an additional entry in the history and thus the back button would be broken (going back would land on the URL causing a new redirect). Additionally, there was a bug where the redirect, even without urlUpdateStrategy="eager", could create a history with too many entries. This was due to kicking off a new navigation within the navigation cancelling logic. With this PR the new navigation is pushed to the next tick with a setTimeout, allowing the page being redirected from to be cancelled before starting a new navigation. Related to #27148 fix(router): adjust UrlTree redirect to replace URL if in eager update Fix lint errors PR Close #32988
…ras (angular#33029) There is some confusion around which `NavigationExtras` values are used by createUrlTree. This specifies that only values that change the URL are used. This came up during the discussion in angular#27148. PR Close angular#33029
angular#32988) Resubmit angular#31168 now that google3 tests can pass. This requires http://cl/272696717 to be patched. Original description from jasonaden: Without this change when using UrlTree redirects in urlUpdateStrategy="eager", the URL would get updated to the target location, then redirected. This resulted in having an additional entry in the history and thus the back button would be broken (going back would land on the URL causing a new redirect). Additionally, there was a bug where the redirect, even without urlUpdateStrategy="eager", could create a history with too many entries. This was due to kicking off a new navigation within the navigation cancelling logic. With this PR the new navigation is pushed to the next tick with a setTimeout, allowing the page being redirected from to be cancelled before starting a new navigation. Related to angular#27148 fix(router): adjust UrlTree redirect to replace URL if in eager update Fix lint errors PR Close angular#32988
angular#32988) Resubmit angular#31168 now that google3 tests can pass. This requires http://cl/272696717 to be patched. Original description from jasonaden: Without this change when using UrlTree redirects in urlUpdateStrategy="eager", the URL would get updated to the target location, then redirected. This resulted in having an additional entry in the history and thus the back button would be broken (going back would land on the URL causing a new redirect). Additionally, there was a bug where the redirect, even without urlUpdateStrategy="eager", could create a history with too many entries. This was due to kicking off a new navigation within the navigation cancelling logic. With this PR the new navigation is pushed to the next tick with a setTimeout, allowing the page being redirected from to be cancelled before starting a new navigation. Related to angular#27148 fix(router): adjust UrlTree redirect to replace URL if in eager update Fix lint errors PR Close angular#32988
|
Please don't forget about some sort of feature parity with Being able to redirect elegantly from a Resolver is very important - especially in the case where an invalid URL is provided (eg. a mistyped ID). |
|
Hey, wanted to know the status of this feature request. No update for few months now, was it missed again? |
|
We are using a route guard to redirect users to a loading page before our route resolvers run. Without a loading page, if an authenticated user navigates directly to a url that requires a lot of data to be resolved, the user is presented with a blank white page with nothing indicating anything is working. Not ideal. To get around this, we have implemented a loading page. We'd like the loading page url to never be seen by our users. However, because the |
|
You may be better off with some sort of view cover indicator that exists at a root level to indicate the route is loading: I think you'll run into issues eventually for more complicated scenarios with redirecting from the intended route and back |
|
In all the cases at work we handle fetching and loading indicators inside page components Personally I do not understand the need of resolvers (now and in angularjs) |
|
We handle it using guards, redirect to another page if it hits the criteria. |
|
I had the same idea in mind when I was writing in my guard something like this: |
|
Also related to #16981 |
|
The use case I have is similar to #17004 - being able to render a 404 response for the current route. I don't want to redirect to a '404' / 'not-found' route. |
|
+1 for this feature request, I was honestly surprised that you can't utilize anything like navigationExtras in a guard when you can do it everywhere else. I was hoping to build some process validation into my navigation guards to prevent users from getting clever and trying to bypass the usual workflow by changing the URL. I can still do that the way thing are now, but I can't funnel any custom data back through the navigationExtras in a neat and readable way. I was initially thinking a good solution would be to provide some sort of "alternateRouterCommand" interface on the guard itself, but I think I might have a better solution that covers more scenarios. Would it be reasonable to provide an "onGuarded" callback property on a Route that aggregates the values returned by negative guards into an array until all guards have been processed, then calls the function you provide in the "onGuarded" property with the array of values returned from failed guards, target route, and current route? That would allow developers to build dynamic responses from rejected routes, predict the order in which guards will run, and put a lot of their error handling in a neat, centralized location. |
|
Are any changes? |
Did you manage to come up with a solution to this issue? I am trying to implement the same 403 use case you mentioned. Thanks! |
|
@timminss not a good one, I'm afraid. We did something similar to the below code snippet, but using a generic "error" page for various errors instead of a specific route for each error. The errors generated are from the This was the best we could come up with 2-3 years ago when we wrote the code. We weren't able to find much documentation for more advanced routing use-cases at the time. @Injectable()
export class Route403Service {
constructor(private router: Router, private location: Location) {}
init() {
let navError: NavigationError;
this.router.events
.pipe(filter((e): e is NavigationError => e instanceof NavigationError))
.subscribe(e => navError = e);
this.router.errorHandler = err => {
if (err.status === 403) {
this.router.navigate(['/not-found'], { skipLocationChange: true }).then(() => {
// Show the route that couldn't be navigated to in the browser
this.location.replaceState(navError.url);
});
}
};
}
} |
|
@csvn is there a reason you used |
|
Is some new progress with that? |
|
Hi, no progress or ETA on this, but here to provide some alternative workaround hackery for the TL;DR - for the Two pre-understanding points:
Because of the above two points, you can actually simply do |
I would say this is almost good, because the const currentPath = this.router.getCurrentNavigation()?.extractedUrl.toString();I needed to use it in an {
if (currentPath) {
this.location.replaceState(currentPath);
}
});
return EMPTY;
}
return throwError(error);
}
}
">
@Injectable() export class ErrorResponseInterceptor implements HttpInterceptor { constructor(private router: Router, private location: Location) {} intercept( request: HttpRequest<unknown>, next: HttpHandler ): Observable<HttpEvent<unknown>> { return next.handle(request).pipe( // retry(1), catchError((error: HttpErrorResponse) => { return this.handleError(error); }) ); } handleError(error: HttpErrorResponse) { const currentPath = this.router .getCurrentNavigation() ?.extractedUrl.toString(); if (error) { let targetPath; switch (error.status) { case 500: targetPath = '500'; break; case 404: targetPath = '**'; break; default: targetPath = '**'; break; } this.router .navigateByUrl(targetPath, { skipLocationChange: true }) .then(() => { if (currentPath) { this.location.replaceState(currentPath); } }); return EMPTY; } return throwError(error); } } Of course routing must be defined somewhere, ideally in some eagerly loaded module , e.g. the app-routing.module.ts : //... some paths refined in the Routes array and then this at the end
{ path: '500', component: P500Component },
{ path: '**', component: P404Component }, // Wildcard route for a 404 pageand interceptor registered in the providers array of the app.module.ts : //...
// interceptor order matters !
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: ErrorResponseInterceptor,
multi: true,
},
//... |
Relevant Package
This feature request is for @angular/router
Description
In #26521 (Angular 7.1.0), a feature was introduced that allows returning a
URLTreefrom guards in order to initiate a redirect. However, this doesn't seem to allow usingNavigationExtrassuch asskipLocationChange.Since the change in #26521 wasn't just syntax sugar, but is relevant to have predictable behavior when dealing with multiple guards, it should be considered to somehow allow using these options when returning a
URLTreeas well.Describe the solution you'd like
I don't have a really good proposal at this moment apart from next to returning
URLTreealso returning some kind ofsuch that I could use
I guess the exact typing of the guard would have to be
Admittedly, the return type of this function is growing increasingly complex with this.
Describe alternatives you've considered
Possible options that I don't think would make good solutions would be
[URLTree, NavigationExtras](this just feels unclean)The text was updated successfully, but these errors were encountered: