Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@
"fireActivationStart",
"fireChildActivationStart",
"first",
"firstValueFrom",
"flatten",
"flattenRouteTree",
"forEachSingleProvider",
Expand Down
11 changes: 7 additions & 4 deletions packages/router/src/apply_redirects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {ActivatedRouteSnapshot} from './router_state';
import {Params, PRIMARY_OUTLET} from './shared';
import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
import {wrapIntoObservable} from './utils/collection';
import {firstValueFrom} from './utils/first_value_from';

export const NO_MATCH_ERROR_NAME = 'ɵNoMatch';
export class NoMatch extends Error {
Expand Down Expand Up @@ -195,9 +196,11 @@ function getRedirectResult(
}
const redirectToFn = redirectTo;
const {queryParams, fragment, routeConfig, url, outlet, params, data, title} = currentSnapshot;
return wrapIntoObservable(
runInInjectionContext(injector, () =>
redirectToFn({params, data, queryParams, fragment, routeConfig, url, outlet, title}),
return firstValueFrom(
wrapIntoObservable(
runInInjectionContext(injector, () =>
redirectToFn({params, data, queryParams, fragment, routeConfig, url, outlet, title}),
),
),
).toPromise() as Promise<string | UrlTree>;
);
}
24 changes: 8 additions & 16 deletions packages/router/src/recognize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
split,
} from './utils/config_matching';
import {TreeNode} from './utils/tree';
import {firstValueFrom} from './utils/first_value_from';

/**
* Class used to indicate there were no additional route config matches but that all segments of
Expand Down Expand Up @@ -400,14 +401,9 @@ export class Recognizer {
if (this.abortSignal.aborted) {
throw new Error(this.abortSignal.reason);
}
const result = await matchWithChecks(
rawSegment,
route,
segments,
injector,
this.urlSerializer,
this.abortSignal,
).toPromise();
const result = await firstValueFrom(
matchWithChecks(rawSegment, route, segments, injector, this.urlSerializer, this.abortSignal),
);
if (route.path === '**') {
// Prior versions of the route matching algorithm would stop matching at the wildcard route.
// We should investigate a better strategy for any existing children. Otherwise, these
Expand Down Expand Up @@ -500,15 +496,11 @@ export class Recognizer {
if (this.abortSignal.aborted) {
throw new Error(this.abortSignal.reason);
}
const shouldLoadResult = await runCanLoadGuards(
injector,
route,
segments,
this.urlSerializer,
this.abortSignal,
).toPromise();
const shouldLoadResult = await firstValueFrom(
runCanLoadGuards(injector, route, segments, this.urlSerializer, this.abortSignal),
);
if (shouldLoadResult) {
const cfg = await this.configLoader.loadChildren(injector, route).toPromise();
const cfg = await firstValueFrom(this.configLoader.loadChildren(injector, route));
if (!cfg) {
throw canLoadFails(route);
}
Expand Down
20 changes: 20 additions & 0 deletions packages/router/src/utils/first_value_from.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/

import {Observable} from 'rxjs';
import {first} from 'rxjs/operators';

/** replacement for firstValueFrom in rxjs 7. We must support rxjs v6 so we cannot use it */
export function firstValueFrom<T>(source: Observable<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
source.pipe(first()).subscribe({
next: (value) => resolve(value),
error: (err) => reject(err),
});
});
}
25 changes: 23 additions & 2 deletions packages/router/test/apply_redirects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

import {EnvironmentInjector, inject, Injectable, Type} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {firstValueFrom, Observable, of} from 'rxjs';
import {switchMap, tap} from 'rxjs/operators';
import {firstValueFrom, interval, Observable, of} from 'rxjs';
import {map, switchMap, tap} from 'rxjs/operators';

import {Route, Routes} from '../src/models';
import {recognize} from '../src/recognize';
Expand Down Expand Up @@ -1819,6 +1819,27 @@ describe('redirects', () => {
},
);
});

it('works when the returned redirect observable does not complete', async () => {
await checkRedirect(
[
{
path: 'a',
children: [
{
path: 'b',
redirectTo: () => interval(100).pipe(map(() => '/redirected')),
},
],
},
{path: '**', component: ComponentC},
],
'/a;k1=v1;k2=v2/b;k3=v3;k4=v4',
(t: UrlTree) => {
expectTreeToBe(t, 'redirected');
},
);
});
});

// internal failure b/165719418
Expand Down