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
39 changes: 17 additions & 22 deletions packages/core/test/acceptance/injector_profiler_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ describe('getInjectorMetadata', () => {
});
afterEach(() => setInjectorProfiler(null));

it('should be able to determine injector type and name', fakeAsync(() => {
it('should be able to determine injector type and name', async () => {
class MyServiceA {}
@NgModule({providers: [MyServiceA]})
class ModuleA {}
Expand Down Expand Up @@ -500,8 +500,7 @@ describe('getInjectorMetadata', () => {
});

const root = TestBed.createComponent(MyStandaloneComponent);
TestBed.inject(Router).navigateByUrl('/lazy');
tick();
await TestBed.inject(Router).navigateByUrl('/lazy');
root.detectChanges();

function afterLazyComponentRendered(lazyComponent: LazyComponent) {
Expand Down Expand Up @@ -537,7 +536,7 @@ describe('getInjectorMetadata', () => {
expect(injectorMetadata[4]!.type).toBe('environment');
expect(injectorMetadata[5]!.type).toBe('environment');
}
}));
});

it('should return null for injectors it does not recognize', () => {
class MockInjector extends Injector {
Expand Down Expand Up @@ -814,7 +813,7 @@ describe('getInjectorProviders', () => {
expect(myServiceBProvider!.importPath![2]).toBe(ModuleC);
});

it('should be able to determine import paths after module provider flattening in the standalone component case with lazy components', fakeAsync(() => {
it('should be able to determine import paths after module provider flattening in the standalone component case with lazy components', async () => {
class MyService {}

@NgModule({providers: [MyService]})
Expand Down Expand Up @@ -850,8 +849,7 @@ describe('getInjectorProviders', () => {
],
});
const root = TestBed.createComponent(MyStandaloneComponent);
TestBed.inject(Router).navigateByUrl('/lazy');
tick();
await TestBed.inject(Router).navigateByUrl('/lazy');
root.detectChanges();

const myStandaloneComponentNodeInjector = root.componentRef.injector;
Expand Down Expand Up @@ -883,9 +881,9 @@ describe('getInjectorProviders', () => {
expect(myServiceProviderRecord!.importPath!.length).toBe(2);
expect(myServiceProviderRecord!.importPath![0]).toBe(MyStandaloneComponentB);
expect(myServiceProviderRecord!.importPath![1]).toBe(ModuleA);
}));
});

it('should be able to determine providers in a lazy route that has providers', fakeAsync(() => {
it('should be able to determine providers in a lazy route that has providers', async () => {
class MyService {}

@Component({selector: 'my-comp-b', template: 'hello world'})
Expand Down Expand Up @@ -915,8 +913,7 @@ describe('getInjectorProviders', () => {
],
});
const root = TestBed.createComponent(MyStandaloneComponent);
TestBed.inject(Router).navigateByUrl('/lazy');
tick();
await TestBed.inject(Router).navigateByUrl('/lazy');
root.detectChanges();

const myStandalonecomponentB = root.componentRef.instance!.routerOutlet!
Expand All @@ -935,7 +932,7 @@ describe('getInjectorProviders', () => {
expect(myServiceProviderRecord).toBeTruthy();
expect(myServiceProviderRecord!.provider).toBe(MyService);
expect(myServiceProviderRecord!.token).toBe(MyService);
}));
});

it('should be able to determine providers in an injector that was created manually', fakeAsync(() => {
class MyService {}
Expand Down Expand Up @@ -1034,7 +1031,7 @@ describe('getDependenciesFromInjectable', () => {
});
afterEach(() => setInjectorProfiler(null));

it('should be able to determine which injector dependencies come from', fakeAsync(() => {
it('should be able to determine which injector dependencies come from', async () => {
class MyService {}
class MyServiceB {}
class MyServiceC {}
Expand Down Expand Up @@ -1109,8 +1106,7 @@ describe('getDependenciesFromInjectable', () => {
});

const root = TestBed.createComponent(MyStandaloneComponent);
TestBed.inject(Router).navigateByUrl('/lazy');
tick();
await TestBed.inject(Router).navigateByUrl('/lazy');
root.detectChanges();

const myStandalonecomponentB = root.componentRef.instance!.routerOutlet!
Expand Down Expand Up @@ -1232,9 +1228,9 @@ describe('getDependenciesFromInjectable', () => {
// The NodeInjector that provides MyService is not in the host path of this injector.
expect(deps!.dependencies[0].providedIn).toBeUndefined();
}
}));
});

it('should be able to recursively determine dependencies of dependencies by using the providedIn field', fakeAsync(() => {
it('should be able to recursively determine dependencies of dependencies by using the providedIn field', async () => {
@Injectable()
class MyService {
myServiceB = inject(MyServiceB);
Expand Down Expand Up @@ -1318,7 +1314,7 @@ describe('getDependenciesFromInjectable', () => {
host: false,
});
expect(routerDependency.providedIn).toBe((standaloneInjector as R3Injector).parent);
}));
});
});

describe('getInjectorResolutionPath', () => {
Expand All @@ -1328,7 +1324,7 @@ describe('getInjectorResolutionPath', () => {
});
afterEach(() => setInjectorProfiler(null));

it('should be able to inspect injector hierarchy structure', fakeAsync(() => {
it('should be able to inspect injector hierarchy structure', async () => {
class MyServiceA {}
@NgModule({providers: [MyServiceA]})
class ModuleA {}
Expand Down Expand Up @@ -1368,8 +1364,7 @@ describe('getInjectorResolutionPath', () => {
],
});
const root = TestBed.createComponent(MyStandaloneComponent);
TestBed.inject(Router).navigateByUrl('/lazy');
tick();
await TestBed.inject(Router).navigateByUrl('/lazy');
root.detectChanges();

function onLazyComponentCreated() {
Expand Down Expand Up @@ -1464,5 +1459,5 @@ describe('getInjectorResolutionPath', () => {

expect(path[6]).toBeInstanceOf(NullInjector);
}
}));
});
});
4 changes: 2 additions & 2 deletions packages/core/test/bundling/defer/bundle.golden_symbols.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"shimStylesContent"
],
"lazy": [
"DeferComponent",
"AFTER_RENDER_SEQUENCES_TO_ADD",
"ANIMATIONS_DISABLED",
"APP_BOOTSTRAP_LISTENER",
Expand Down Expand Up @@ -753,8 +754,7 @@
"wasLastNodeCreated",
"writeDirectClass",
"writeDirectStyle",
"writeToDirectiveInput",
"DeferComponent"
"writeToDirectiveInput"
]
}
}
9 changes: 5 additions & 4 deletions packages/core/test/bundling/router/bundle.golden_symbols.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"chunks": {
"main": [
"ABSOLUTE_REDIRECT_ERROR_NAME",
"AFTER_RENDER_SEQUENCES_TO_ADD",
"ANIMATIONS_DISABLED",
"APP_BASE_HREF",
Expand Down Expand Up @@ -177,6 +178,7 @@
"NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR",
"NOT_YET",
"NO_CHANGE",
"NO_MATCH_ERROR_NAME",
"NO_PARENT_INJECTOR",
"NULL_INJECTOR",
"Navigation",
Expand Down Expand Up @@ -232,6 +234,7 @@
"REACTIVE_LVIEW_CONSUMER_NODE",
"REACTIVE_NODE",
"REACTIVE_TEMPLATE_CONSUMER",
"RECOGNIZE_IMPL",
"REFERENCE_NODE_BODY",
"REFERENCE_NODE_HOST",
"REF_EXTRACTOR_REGEXP",
Expand Down Expand Up @@ -395,6 +398,7 @@
"_stripIndexHtml",
"_stripOrigin",
"_wasLastNodeCreated",
"abortSignalToObservable",
"activateRoutes",
"activeConsumer",
"addAfterRenderSequencesForView",
Expand Down Expand Up @@ -864,7 +868,6 @@
"iterator",
"joinWithSlash",
"last",
"last2",
"last3",
"lastNodeWasCreated",
"lastSelectedElementIdx",
Expand Down Expand Up @@ -930,7 +933,6 @@
"ngZoneInstanceId",
"noLeftoversInUrl",
"noMatch",
"noMatch",
"noSideEffects",
"nodeChildrenAsMap",
"noop",
Expand Down Expand Up @@ -1028,8 +1030,6 @@
"saveContentQueryAndDirectiveIndex",
"saveNameToExportMap",
"saveResolvedLocalsInData",
"scan",
"scanInternals",
"scheduleArray",
"scheduleAsyncIterable",
"scheduleCallbackWithMicrotask",
Expand Down Expand Up @@ -1117,6 +1117,7 @@
"take",
"takeLast",
"takeUntil",
"takeUntilAbort",
"tap",
"textBindingInternal",
"throwError",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2585,6 +2585,7 @@ describe('platform-server partial hydration integration', () => {

const routeLink = doc.getElementById('route-link')!;
routeLink.click();
await appRef.whenStable();
await allPendingDynamicImports();
appRef.tick();

Expand Down
92 changes: 40 additions & 52 deletions packages/router/src/apply_redirects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,57 +7,48 @@
*/

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

import {RuntimeErrorCode} from './errors';
import {NavigationCancellationCode} from './events';
import {LoadedRouterConfig, RedirectFunction, Route} from './models';
import {RedirectFunction, Route} from './models';
import {navigationCancelingError} from './navigation_canceling_error';
import {ActivatedRouteSnapshot} from './router_state';
import {Params, PRIMARY_OUTLET} from './shared';
import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
import {wrapIntoObservable} from './utils/collection';

export class NoMatch {
export const NO_MATCH_ERROR_NAME = 'ɵNoMatch';
export class NoMatch extends Error {
override readonly name: string = NO_MATCH_ERROR_NAME;
public segmentGroup: UrlSegmentGroup | null;

constructor(segmentGroup?: UrlSegmentGroup) {
super();
this.segmentGroup = segmentGroup || null;
}
}

export const ABSOLUTE_REDIRECT_ERROR_NAME = 'ɵAbsoluteRedirect';
export class AbsoluteRedirect extends Error {
override readonly name: string = ABSOLUTE_REDIRECT_ERROR_NAME;
constructor(public urlTree: UrlTree) {
super();
}
}

export function noMatch(segmentGroup: UrlSegmentGroup): Observable<any> {
return throwError(new NoMatch(segmentGroup));
}

export function absoluteRedirect(newTree: UrlTree): Observable<any> {
return throwError(new AbsoluteRedirect(newTree));
}

export function namedOutletsRedirect(redirectTo: string): Observable<any> {
return throwError(
new RuntimeError(
RuntimeErrorCode.NAMED_OUTLET_REDIRECT,
(typeof ngDevMode === 'undefined' || ngDevMode) &&
`Only absolute redirects can have named outlets. redirectTo: '${redirectTo}'`,
),
export function namedOutletsRedirect(redirectTo: string): never {
throw new RuntimeError(
RuntimeErrorCode.NAMED_OUTLET_REDIRECT,
(typeof ngDevMode === 'undefined' || ngDevMode) &&
`Only absolute redirects can have named outlets. redirectTo: '${redirectTo}'`,
);
}

export function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
return throwError(
navigationCancelingError(
(typeof ngDevMode === 'undefined' || ngDevMode) &&
`Cannot load children because the guard of the route "path: '${route.path}'" returned false`,
NavigationCancellationCode.GuardRejected,
),
export function canLoadFails(route: Route): never {
throw navigationCancelingError(
(typeof ngDevMode === 'undefined' || ngDevMode) &&
`Cannot load children because the guard of the route "path: '${route.path}'" returned false`,
NavigationCancellationCode.GuardRejected,
);
}

Expand All @@ -67,49 +58,46 @@ export class ApplyRedirects {
private urlTree: UrlTree,
) {}

lineralizeSegments(route: Route, urlTree: UrlTree): Observable<UrlSegment[]> {
async lineralizeSegments(route: Route, urlTree: UrlTree): Promise<UrlSegment[]> {
let res: UrlSegment[] = [];
let c = urlTree.root;
while (true) {
res = res.concat(c.segments);
if (c.numberOfChildren === 0) {
return of(res);
return res;
}

if (c.numberOfChildren > 1 || !c.children[PRIMARY_OUTLET]) {
return namedOutletsRedirect(`${route.redirectTo!}`);
throw namedOutletsRedirect(`${route.redirectTo!}`);
}

c = c.children[PRIMARY_OUTLET];
}
}

applyRedirectCommands(
async applyRedirectCommands(
segments: UrlSegment[],
redirectTo: string | RedirectFunction,
posParams: {[k: string]: UrlSegment},
currentSnapshot: ActivatedRouteSnapshot,
injector: Injector,
): Observable<UrlTree> {
return getRedirectResult(redirectTo, currentSnapshot, injector).pipe(
map((redirect) => {
if (redirect instanceof UrlTree) {
throw new AbsoluteRedirect(redirect);
}

const newTree = this.applyRedirectCreateUrlTree(
redirect,
this.urlSerializer.parse(redirect),
segments,
posParams,
);

if (redirect[0] === '/') {
throw new AbsoluteRedirect(newTree);
}
return newTree;
}),
): Promise<UrlTree> {
const redirect = await getRedirectResult(redirectTo, currentSnapshot, injector);
if (redirect instanceof UrlTree) {
throw new AbsoluteRedirect(redirect);
}

const newTree = this.applyRedirectCreateUrlTree(
redirect,
this.urlSerializer.parse(redirect),
segments,
posParams,
);

if (redirect[0] === '/') {
throw new AbsoluteRedirect(newTree);
}
return newTree;
}

applyRedirectCreateUrlTree(
Expand Down Expand Up @@ -201,15 +189,15 @@ function getRedirectResult(
redirectTo: string | RedirectFunction,
currentSnapshot: ActivatedRouteSnapshot,
injector: Injector,
): Observable<string | UrlTree> {
): Promise<string | UrlTree> {
if (typeof redirectTo === 'string') {
return of(redirectTo);
return Promise.resolve(redirectTo);
}
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}),
),
);
).toPromise() as Promise<string | UrlTree>;
}
Loading