Skip to content

Commit 61ec9c7

Browse files
committed
fix(@angular/ssr): disable component bootstrapping during route extraction
This commit disables component bootstrapping during route extraction to prevent invoking the AppComponent and its lifecycle hooks. Closes #29085
1 parent 210bf4e commit 61ec9c7

File tree

3 files changed

+63
-11
lines changed

3 files changed

+63
-11
lines changed

packages/angular/ssr/src/routes/ng-routes.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,16 @@
77
*/
88

99
import { APP_BASE_HREF, PlatformLocation } from '@angular/common';
10-
import { ApplicationRef, Compiler, Injector, runInInjectionContext, ɵConsole } from '@angular/core';
10+
import {
11+
APP_INITIALIZER,
12+
ApplicationRef,
13+
Compiler,
14+
ComponentRef,
15+
inject,
16+
Injector,
17+
runInInjectionContext,
18+
ɵConsole,
19+
} from '@angular/core';
1120
import { INITIAL_CONFIG, platformServer } from '@angular/platform-server';
1221
import {
1322
Route as AngularRoute,
@@ -479,6 +488,18 @@ export async function getRoutesFromAngularRouterConfig(
479488
provide: ɵConsole,
480489
useFactory: () => new Console(),
481490
},
491+
{
492+
// We cannot replace `ApplicationRef` with a different provider here due to the dependency injection (DI) hierarchy.
493+
// This code is running at the platform level, where `ApplicationRef` is provided in the root injector.
494+
// As a result, any attempt to replace it will cause the root provider to override the platform provider.
495+
// TODO(alanagius): investigate exporting the app config directly which would help with: https://github.com/angular/angular/issues/59144
496+
provide: APP_INITIALIZER,
497+
multi: true,
498+
useFactory: () => () => {
499+
const appRef = inject(ApplicationRef);
500+
appRef.bootstrap = () => undefined as unknown as ComponentRef<unknown>;
501+
},
502+
},
482503
]);
483504

484505
try {

packages/angular/ssr/test/routes/ng-routes_spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,4 +466,33 @@ describe('extractRoutesAndCreateRouteTree', () => {
466466
{ route: '/example/home', renderMode: RenderMode.Server },
467467
]);
468468
});
469+
470+
it('should not bootstrap the root component', async () => {
471+
@Component({
472+
standalone: true,
473+
selector: 'app-root',
474+
template: '',
475+
})
476+
class RootComponent {
477+
constructor() {
478+
throw new Error('RootComponent should not be bootstrapped.');
479+
}
480+
}
481+
482+
setAngularAppTestingManifest(
483+
[
484+
{ path: '', component: DummyComponent },
485+
{ path: 'home', component: DummyComponent },
486+
],
487+
[{ path: '**', renderMode: RenderMode.Server }],
488+
undefined,
489+
undefined,
490+
undefined,
491+
RootComponent,
492+
);
493+
494+
const { routeTree, errors } = await extractRoutesAndCreateRouteTree({ url });
495+
expect(errors).toHaveSize(0);
496+
expect(routeTree.toObject()).toHaveSize(2);
497+
});
469498
});

packages/angular/ssr/test/testing-utils.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,22 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import { Component, provideExperimentalZonelessChangeDetection } from '@angular/core';
9+
import { Component, provideExperimentalZonelessChangeDetection, Type } from '@angular/core';
1010
import { bootstrapApplication } from '@angular/platform-browser';
1111
import { provideServerRendering } from '@angular/platform-server';
1212
import { RouterOutlet, Routes, provideRouter } from '@angular/router';
1313
import { destroyAngularServerApp } from '../src/app';
1414
import { ServerAsset, setAngularAppManifest } from '../src/manifest';
1515
import { ServerRoute, provideServerRoutesConfig } from '../src/routes/route-config';
1616

17+
@Component({
18+
standalone: true,
19+
selector: 'app-root',
20+
template: '<router-outlet />',
21+
imports: [RouterOutlet],
22+
})
23+
class AppComponent {}
24+
1725
/**
1826
* Configures the Angular application for testing by setting up the Angular app manifest,
1927
* configuring server-side rendering, and bootstrapping the application with the provided routes.
@@ -26,24 +34,18 @@ import { ServerRoute, provideServerRoutesConfig } from '../src/routes/route-conf
2634
* @param additionalServerAssets - A record of additional server assets to include,
2735
* where the keys are asset paths and the values are asset details.
2836
* @param locale - An optional locale to configure for the application during testing.
37+
* @param rootComponent - The root Angular component to bootstrap the application.
2938
*/
3039
export function setAngularAppTestingManifest(
3140
routes: Routes,
3241
serverRoutes: ServerRoute[],
3342
baseHref = '/',
3443
additionalServerAssets: Record<string, ServerAsset> = {},
3544
locale?: string,
45+
rootComponent: Type<unknown> = AppComponent,
3646
): void {
3747
destroyAngularServerApp();
3848

39-
@Component({
40-
standalone: true,
41-
selector: 'app-root',
42-
template: '<router-outlet />',
43-
imports: [RouterOutlet],
44-
})
45-
class AppComponent {}
46-
4749
setAngularAppManifest({
4850
inlineCriticalCss: false,
4951
baseHref,
@@ -81,7 +83,7 @@ export function setAngularAppTestingManifest(
8183
},
8284
},
8385
bootstrap: async () => () => {
84-
return bootstrapApplication(AppComponent, {
86+
return bootstrapApplication(rootComponent, {
8587
providers: [
8688
provideServerRendering(),
8789
provideExperimentalZonelessChangeDetection(),

0 commit comments

Comments
 (0)