angular-cli icon indicating copy to clipboard operation
angular-cli copied to clipboard

[Bug] Route-level Environment Initializer Skipped During AppEngine Route Discovery

Open Char2sGu opened this issue 11 months ago • 3 comments

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

Char2sGu avatar Jan 25 '25 23:01 Char2sGu

Addition: such environment initializers used to work with Angular v19.0. After upgrading to Angular v19.1, they are skipped.

Char2sGu avatar Jan 25 '25 23:01 Char2sGu

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?

alan-agius4 avatar Jan 26 '25 19:01 alan-agius4

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,
      },
    ],
  }),
];

Char2sGu avatar Jan 27 '25 00:01 Char2sGu

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.