Skip to content

fix(@angular/ssr): unblock route extraction with withEnabledBlockingInitialNavigation #29411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
10 changes: 8 additions & 2 deletions packages/angular/ssr/src/routes/ng-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,11 +523,17 @@ export async function getRoutesFromAngularRouterConfig(
applicationRef = await bootstrap();
}

const injector = applicationRef.injector;
const router = injector.get(Router);

// Workaround to unblock navigation when `withEnabledBlockingInitialNavigation()` is used.
// This is necessary because route extraction disables component bootstrapping.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(router as any).navigationTransitions.afterPreactivation()?.next?.();

// Wait until the application is stable.
await applicationRef.whenStable();

const injector = applicationRef.injector;
const router = injector.get(Router);
const routesResults: RouteTreeNodeMetadata[] = [];
const errors: string[] = [];

Expand Down
33 changes: 33 additions & 0 deletions packages/angular/ssr/test/routes/ng-routes_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import '@angular/compiler';
/* eslint-enable import/no-unassigned-import */

import { Component } from '@angular/core';
import { Routes, provideRouter, withEnabledBlockingInitialNavigation } from '@angular/router';
import { extractRoutesAndCreateRouteTree } from '../../src/routes/ng-routes';
import { PrerenderFallback, RenderMode } from '../../src/routes/route-config';
import { setAngularAppTestingManifest } from '../testing-utils';
Expand Down Expand Up @@ -495,4 +496,36 @@ describe('extractRoutesAndCreateRouteTree', () => {
expect(errors).toHaveSize(0);
expect(routeTree.toObject()).toHaveSize(2);
});

it('should not bootstrap the root component when using `withEnabledBlockingInitialNavigation`', async () => {
@Component({
standalone: true,
selector: 'app-root',
template: '',
})
class RootComponent {
constructor() {
throw new Error('RootComponent should not be bootstrapped.');
}
}

const routes: Routes = [
{ path: '', component: DummyComponent },
{ path: 'home', component: DummyComponent },
];

setAngularAppTestingManifest(
routes,
[{ path: '**', renderMode: RenderMode.Server }],
undefined,
undefined,
undefined,
RootComponent,
[provideRouter(routes, withEnabledBlockingInitialNavigation())],
);

const { routeTree, errors } = await extractRoutesAndCreateRouteTree({ url });
expect(errors).toHaveSize(0);
expect(routeTree.toObject()).toHaveSize(2);
});
});
12 changes: 11 additions & 1 deletion packages/angular/ssr/test/testing-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
* found in the LICENSE file at https://angular.dev/license
*/

import { Component, Type, provideExperimentalZonelessChangeDetection } from '@angular/core';
import {
Component,
EnvironmentProviders,
Provider,
Type,
provideExperimentalZonelessChangeDetection,
} from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { provideServerRendering } from '@angular/platform-server';
import { RouterOutlet, Routes, provideRouter } from '@angular/router';
Expand Down Expand Up @@ -35,6 +41,8 @@ class AppComponent {}
* where the keys are asset paths and the values are asset details.
* @param locale - An optional locale to configure for the application during testing.
* @param rootComponent - The root Angular component to bootstrap the application.
* @param extraProviders - An optional array of additional providers that should be available to the
* root component and all its children.
*/
export function setAngularAppTestingManifest(
routes: Routes,
Expand All @@ -43,6 +51,7 @@ export function setAngularAppTestingManifest(
additionalServerAssets: Record<string, ServerAsset> = {},
locale?: string,
rootComponent: Type<unknown> = AppComponent,
extraProviders: Array<Provider | EnvironmentProviders> = [],
): void {
destroyAngularServerApp();

Expand Down Expand Up @@ -89,6 +98,7 @@ export function setAngularAppTestingManifest(
provideExperimentalZonelessChangeDetection(),
provideRouter(routes),
provideServerRoutesConfig(serverRoutes),
...extraProviders,
],
});
},
Expand Down
Loading