8000 feat(router): add asynchronous redirects · angular/angular@5c7d909 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5c7d909

Browse files
MeddahAbdellahatscott
authored andcommitted
feat(router): add asynchronous redirects
Adds support for asynchronous redirects in the router, allowing redirect logic to be resolved dynamically (e.g., via API or async function). This enhances routing flexibility and supports more complex navigation scenarios. BREAKING CHANGE: The `RedirectFn` can now return `Observable` or `Promise`. Any code that directly calls functions returning this type may need to be adjusted to account for this.
1 parent c0ef923 commit 5c7d909

File tree

5 files changed

+660
-41
lines changed

5 files changed

+660
-41
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ export class RedirectCommand {
606606
}
607607

608608
// @public
609-
export type RedirectFunction = (redirectData: Pick<ActivatedRouteSnapshot, 'routeConfig' | 'url' | 'params' | 'queryParams' | 'fragment' | 'data' | 'outlet' | 'title'>) => string | UrlTree;
609+
export type RedirectFunction = (redirectData: Pick<ActivatedRouteSnapshot, 'routeConfig' | 'url' | 'params' | 'queryParams' | 'fragment' | 'data' | 'outlet' | 'title'>) => MaybeAsync<string | UrlTree>;
610610

611611
// @public
612612
export interface Resolve<T> {

packages/router/src/apply_redirects.ts

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {Injector, runInInjectionContext, ɵRuntimeError as RuntimeError} from '@angular/core';
1010
import {Observable, of, throwError} from 'rxjs';
11+
import {map} from 'rxjs/operators';
1112

1213
import {RuntimeErrorCode} from './errors';
1314
import {NavigationCancellationCode} from './events';
@@ -16,6 +17,7 @@ import {navigationCancelingError} from './navigation_canceling_error';
1617
import {ActivatedRouteSnapshot} from './router_state';
1718
import {Params, PRIMARY_OUTLET} from './shared';
1819
import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
20+
import {wrapIntoObservable} from './utils/collection';
1921

2022
export class NoMatch {
2123
public segmentGroup: UrlSegmentGroup | null;
@@ -88,31 +90,26 @@ export class ApplyRedirects {
8890
posParams: {[k: string]: UrlSegment},
8991
currentSnapshot: ActivatedRouteSnapshot,
9092
injector: Injector,
91-
): UrlTree {
92-
if (typeof redirectTo !== 'string') {
93-
const redirectToFn = redirectTo;
94-
const {queryParams, fragment, routeConfig, url, outlet, params, data, title} =
95-
currentSnapshot;
96-
const newRedirect = runInInjectionContext(injector, () =>
97-
redirectToFn({params, data, queryParams, fragment, routeConfig, url, outlet, title}),
98-
);
99-
if (newRedirect instanceof UrlTree) {
100-
throw new AbsoluteRedirect(newRedirect);
101-
}
102-
103-
redirectTo = newRedirect;
104-
}
105-
106-
const newTree = this.applyRedirectCreateUrlTree(
107-
redirectTo,
108-
this.urlSerializer.parse(redirectTo),
109-
segments,
110-
posParams,
93+
): Observable<UrlTree> {
94+
return getRedirectResult(redirectTo, currentSnapshot, injector).pipe(
95+
map((redirect) => {
96+
if (redirect instanceof UrlTree 8000 ) {
97+
throw new AbsoluteRedirect(redirect);
98+
}
99+
100+
const newTree = this.applyRedirectCreateUrlTree(
101+
redirect,
102+
this.urlSerializer.parse(redirect),
103+
segments,
104+
posParams,
105+
);
106+
107+
if (redirect[0] === '/') {
108+
throw new AbsoluteRedirect(newTree);
109+
}
110+
return newTree;
111+
}),
111112
);
112-
if (redirectTo[0] === '/') {
113-
throw new AbsoluteRedirect(newTree);
114-
}
115-
return newTree;
116113
}
117114

118115
applyRedirectCreateUrlTree(
@@ -199,3 +196,20 @@ export class ApplyRedirects {
199196
return redirectToUrlSegment;
200197
}
201198
}
199+
200+
function getRedirectResult(
201+
redirectTo: string | RedirectFunction,
202+
currentSnapshot: ActivatedRouteSnapshot,
203+
injector: Injector,
204+
): Observable<string | UrlTree> {
205+
if (typeof redirectTo === 'string') {
206+
return of(redirectTo);
207+
}
208+
const redirectToFn = redirectTo;
209+
const {queryParams, fragment, routeConfig, url, outlet, params, data, title} = currentSnapshot;
210+
return wrapIntoObservable(
211+
runInInjectionContext(injector, () =>
212+
redirectToFn({params, data, queryParams, fragment, routeConfig, url, outlet, title}),
213+
),
214+
);
215+
}

packages/router/src/models.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ export type RedirectFunction = (
316316
ActivatedRouteSnapshot,
317317
'routeConfig' | 'url' | 'params' | 'queryParams' | 'fragment' | 'data' | 'outlet' | 'title'
318318
>,
319-
) => string | UrlTree;
319+
) => MaybeAsync<string | UrlTree>;
320320

321321
/**
322322
* A policy for when to run guards and resolvers on a route.

packages/router/src/recognize.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,15 +382,16 @@ export class Recognizer {
382382
const inherited = getInherited(currentSnapshot, parentRoute, this.paramsInheritanceStrategy);
383383
currentSnapshot.params = Object.freeze(inherited.params);
384384
currentSnapshot.data = Object.freeze(inherited.data);
385-
const newTree = this.applyRedirects.applyRedirectCommands(
385+
const newTree$: Observable<UrlTree> = this.applyRedirects.applyRedirectCommands(
386386
consumedSegments,
387387
route.redirectTo!,
388388
positionalParamSegments,
389389
currentSnapshot,
390390
injector,
391391
);
392392

393-
return this.applyRedirects.lineralizeSegments(route, newTree).pipe(
393+
return newTree$.pipe(
394+
switchMap((newTree) => this.applyRedirects.lineralizeSegments(route, newTree)),
394395
mergeMap((newSegments: UrlSegment[]) => {
395396
return this.processSegment(
396397
injector,

0 commit comments

Comments
 (0)
0