FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

feat(router): allow throwing RedirectCommand to trigger redirects · angular/angular@b65dea4 · GitHub

Commit b65dea4

Browse files
authored andcommitted
feat(router): allow throwing RedirectCommand to trigger redirects
This allows developers to throw a `RedirectCommand` directly from guards and resolvers to trigger a redirect. The primary benefit is that we no longer need to pollute the return type of functions that redirect. For example, a deeply nested helper function or a resolver can now simply throw a `RedirectCommand` to short-circuit and redirect, instead of having to return the `UrlTree` or `RedirectCommand` all the way up the call stack. This aligns with prior art in other modern framework routers (such as Next.js, Remix, and SvelteKit), which commonly use thrown exceptions or special redirect responses to abort execution and trigger immediate redirection.
1 parent dd29713 commit b65dea4

4 files changed

Lines changed: 66 additions & 3 deletions

File tree

‎goldens/public-api/router/index.api.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ export function provideRouter(routes: Routes, ...features: RouterFeatures[]): En
597597
export type QueryParamsHandling = 'merge' | 'preserve' | 'replace' | '';
598598

599599
// @public
600-
export class RedirectCommand {
600+
export class RedirectCommand extends Error {
601601
constructor(redirectTo: UrlTree, navigationBehaviorOptions?: NavigationBehaviorOptions | undefined);
602602
// (undocumented)
603603
readonly navigationBehaviorOptions?: NavigationBehaviorOptions | undefined;

‎packages/router/src/models.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,14 @@ export type GuardResult = boolean | UrlTree | RedirectCommand;
117117
*
118118
* @publicApi
119119
*/
120-
export class RedirectCommand {
120+
export class RedirectCommand extends Error {
121121
constructor(
122122
readonly redirectTo: UrlTree,
123123
readonly navigationBehaviorOptions?: NavigationBehaviorOptions,
124-
) {}
124+
) {
125+
super();
126+
Object.setPrototypeOf(this, RedirectCommand.prototype);
127+
}
125128
}
126129

127130
/**

‎packages/router/src/navigation_transition.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,10 @@ export class NavigationTransitions {
902902
return EMPTY;
903903
}
904904

905+
if (e instanceof RedirectCommand) {
906+
e = redirectingNavigationError(this.urlSerializer, e);
907+
}
908+
905909
/* This error type is issued during Redirect, and is handled as a
906910
* cancellation rather than an error. */
907911
if (isNavigationCancelingError(e)) {

‎packages/router/test/integration/guards.spec.ts‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2488,5 +2488,61 @@ export function guardsIntegrationSuite() {
24882488
await router.navigateByUrl('/a?q=2');
24892489
expect(resolveCount).toBe(2);
24902490
});
2491+
2492+
describe('throwing redirect', () => {
2493+
it('should redirect when a guard throws a RedirectCommand', async () => {
2494+
const router = TestBed.inject(Router);
2495+
const location = TestBed.inject(Location);
2496+
const fixture = await createRoot(router, RootCmp);
2497+
2498+
router.resetConfig([
2499+
{
2500+
path: 'a',
2501+
canActivate: [
2502+
() => {
2503+
throw new RedirectCommand(router.parseUrl('/b'));
2504+
},
2505+
],
2506+
component: BlankCmp,
2507+
},
2508+
{
2509+
path: 'b',
2510+
component: BlankCmp,
2511+
},
2512+
]);
2513+
2514+
router.navigateByUrl('/a');
2515+
await advance(fixture);
2516+
2517+
expect(location.path()).toEqual('/b');
2518+
});
2519+
2520+
it('should redirect when a resolver throws a RedirectCommand', async () => {
2521+
const router = TestBed.inject(Router);
2522+
const location = TestBed.inject(Location);
2523+
const fixture = await createRoot(router, RootCmp);
2524+
2525+
router.resetConfig([
2526+
{
2527+
path: 'a',
2528+
resolve: {
2529+
data: () => {
2530+
throw new RedirectCommand(router.parseUrl('/b'));
2531+
},
2532+
},
2533+
component: BlankCmp,
2534+
},
2535+
{
2536+
path: 'b',
2537+
component: BlankCmp,
2538+
},
2539+
]);
2540+
2541+
router.navigateByUrl('/a');
2542+
await advance(fixture);
2543+
2544+
expect(location.path()).toEqual('/b');
2545+
});
2546+
});
24912547
});
24922548
}

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL