[Bug] Route-level Environment Initializer Skipped During AppEngine Route Discovery
Which @angular/* package(s) are the source of the bug?
Don't known / other
Is this a regression?
Yes
Description
In Hybrid Rendering, the AppEngine performs a route discovery run of the application to locate routes. During this run, environment initializers defined at the route level are skipped:
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideClientHydration(withEventReplay()),
provideAppInitializer(() => {
console.error('app init');
}),
provideEnvironmentInitializer(() => {
console.error('root environment init');
}),
],
};
export const routes: Routes = [
{
path: '',
providers: [
provideEnvironmentInitializer(() => {
console.error('route environment init');
}),
],
loadComponent: () =>
import('./test/test.component').then((m) => m.TestComponent),
},
];
When running ng serve:
$ ng serve
Component HMR has been enabled.
If you encounter application reload issues, you can manually reload the page to bypass HMR and/or disable this feature with the `--no-hmr` command line option.
Please consider reporting any issues you encounter here: https://github.com/angular/angular-cli/issues
Browser bundles
Initial chunk files | Names | Raw size
polyfills.js | polyfills | 90.20 kB |
main.js | main | 23.96 kB |
chunk-YQOESBWP.js | - | 754 bytes |
styles.css | styles | 95 bytes |
| Initial total | 115.00 kB
Lazy chunk files | Names | Raw size
chunk-ALIS4MEI.js | test-component | 1.61 kB |
Server bundles
Initial chunk files | Names | Raw size
polyfills.server.mjs | polyfills.server | 572.91 kB |
main.server.mjs | main.server | 25.28 kB |
server.mjs | server | 1.86 kB |
chunk-FFILDKJJ.mjs | - | 788 bytes |
Lazy chunk files | Names | Raw size
chunk-ZIQDPBAX.mjs | test-component | 1.65 kB |
Application bundle generation complete. [0.998 seconds]
Watch mode enabled. Watching for file changes...
NOTE: Raw file sizes do not reflect development server per-request transformations.
➜ Local: http://localhost:4200/
➜ press h + enter to show help
root environment init
app init
root environment init
app init
route environment init
It's easy to notice the route-level environment initializer only run once: it did not run during route discovery.
Please provide a link to a minimal reproduction of the bug
https://github.com/Char2sGu/issue-missing-env-init
Please provide the exception or error you saw
Please provide the environment you discovered this bug in (run ng version)
Angular CLI: 19.1.4
Node: 22.3.0
Package Manager: npm 10.8.1
OS: linux x64
Angular: 19.1.3
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, platform-server
... router
Package Version
---------------------------------------------------------
@angular-devkit/architect 0.1901.4
@angular-devkit/build-angular 19.1.4
@angular-devkit/core 19.1.4
@angular-devkit/schematics 19.1.4
@angular/cli 19.1.4
@angular/ssr 19.1.4
@schematics/angular 19.1.4
rxjs 7.8.1
typescript 5.7.3
zone.js 0.15.0
Anything else?
No response
Addition: such environment initializers used to work with Angular v19.0. After upgrading to Angular v19.1, they are skipped.
This is expected because, during route extraction, navigation to the root page no longer occurs.
What is the use case that requires these to be invoked during extraction?
Hi @alan-agius4 thanks for the feedback. The workaround to https://github.com/angular/angular/issues/51532 is to use a route level environment initializer to get the injector instance, so that loadChildren can be wrapped to run in a injection context:
/**
* Workaround to emulate an injection context in the route's loadChildren resolver.
* @param route
* @returns
* @see https://github.com/angular/angular/issues/51532#issuecomment-1956138610
*/
export function setupInjectionContextForLoadChildren(route: Route): Route {
let injector: Injector | undefined = undefined;
const injectorInitializerProvider: Provider = {
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useFactory:
(instance = inject(Injector)) =>
() => {
injector = instance;
},
};
const transformRoute = (child: Route) => {
if (!child.loadChildren) return child;
const loadChildren = child.loadChildren;
child.loadChildren = (...args) => {
if (!injector) throw new Error('Missing injector');
return runInInjectionContext(injector, () => loadChildren(...args));
};
return child;
};
return {
path: '',
providers: [injectorInitializerProvider],
children: [transformRoute(route)],
};
}
export const APP_ROUTES: Routes = [
setupInjectionContextForLoadChildren({
path: '',
loadChildren: async (providersLoader = inject(MyProvidersLoader)) => [
{
path: '',
providers: await providersLoader.load(),
component: MyComponent,
},
],
}),
];
This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
This action has been performed automatically by a bot.