In Angular, how does provideEnvironmentInitializer differ from provideAppInitializer, and when would you use it in a lazy route?
answer
- per injector, not per application
- synchronous and never awaited
- runs when an injector is created
- route providers create an injector
- app initializers only at the root
basics
~10 sprovideEnvironmentInitializer runs a synchronous function whenever the environment injector holding it is created, and never awaits it. provideAppInitializer runs once at bootstrap and holds rendering until its Promise or Observable settles.
solid answer
~40 s`provideEnvironmentInitializer(fn)` registers a function that runs, in an injection context, as soon as the **environment injector** containing it is created — the root injector at bootstrap, a lazy route's injector the first time the router builds it, or one from `createEnvironmentInjector`. Its type is `() => void`, and Angular never awaits it; returning a Promise does not delay anything. `provideAppInitializer(fn)` is read only from the application's root injector, runs once during bootstrap, after the root's environment initializers, and holds the root component until its async result settles. In a lazy route, `provideEnvironmentInitializer` in the route's `providers` is the way to eagerly start a feature-scoped service, for example registering handlers or starting analytics for that feature; `provideAppInitializer` placed there is not run by bootstrap at all.
code
ts · 15 linesimport { inject, provideEnvironmentInitializer } from '@angular/core';
import { Routes } from '@angular/router';
import { AdminAudit } from './admin/admin-audit';
export const routes: Routes = [
{
path: 'admin',
providers: [
AdminAudit,
// Runs synchronously when the router first creates this route's injector
provideEnvironmentInitializer(() => inject(AdminAudit).start()),
],
loadChildren: () => import('./admin/admin.routes').then((m) => m.adminRoutes),
},
];go deeper
Recall that one initializer is awaited at bootstrap and the other runs synchronously whenever its injector is created.
Explain scope and timing: environment initializers per environment injector and first at the root, application initializers only from the root and awaited.
Use environment initializers to eagerly start feature-scoped services in lazy routes, and catch async work or app initializers placed where they will never be awaited.
Keep startup hooks explicit and cheap: decide which work gates rendering, which is feature-scoped, and which should be lazy altogether.
## Two initializers, two scopes Angular has two provider functions for "run this when things start", and they differ in **what** starts and **whether Angular waits**. | | `provideAppInitializer(fn)` | `provideEnvironmentInitializer(fn)` | |---|---|---| | Runs when | Once, during application bootstrap | Each time the environment injector holding it is created | | Read from | The application's root injector only | Any environment injector | | Function type | Returns `Promise`, `Observable` or `void` | Returns `void` | | Awaited | Yes; the root component waits | Never | | Typical use | Load runtime config or a session before render | Eagerly start or register a service for an injector | | Deprecated predecessor | `APP_INITIALIZER` (v19) | `ENVIRONMENT_INITIALIZER` (v19) | Both run their function inside an injection context, so `inject()` works in the synchronous body. ## What an environment injector is, briefly An **environment injector** is the kind of injector that holds application-level and route-level providers, as opposed to the element injectors attached to components. The root one is built from `ApplicationConfig.providers`. The router builds another one for a route that has a `providers` array, and code can build one with `createEnvironmentInjector()`. Each time such an injector is constructed, Angular resolves its environment initializers — only the ones registered directly on that injector, not its parent's — and calls them in order. ## Timing in the application bootstrap For the root injector, the order is: 1. The root environment injector is created. 2. Its **environment initializers** run synchronously. 3. **Application initializers** are called and their async results awaited. 4. The root component is created. So an environment initializer at the root cannot read anything an application initializer loads; it runs before those are even called. ## Using it in a lazy route A route's `providers` array scopes services to that route subtree. Services there are created lazily, when something injects them. Sometimes you want one created eagerly as soon as the feature is entered — for example a service that registers keyboard shortcuts, subscribes to a message bus, or starts feature analytics, even though no component injects it directly. An environment initializer does that: ```ts { path: 'admin', providers: [AdminAudit, provideEnvironmentInitializer(() => inject(AdminAudit).start())], loadChildren: () => import('./admin/admin.routes'), } ``` The router creates the route's injector the first time it needs it, typically on the first navigation that matches, and caches it on the route. The initializer therefore runs once for that injector, not on every navigation, unless an opt-in router feature destroys unused route injectors and a later navigation creates a new one. ## Mistakes worth naming - **Async work in an environment initializer.** The type is `() => void`, but TypeScript still accepts an `async` function there. Angular ignores the returned Promise: nothing waits for it, and a rejection is not a bootstrap failure. Anything that must finish before render belongs in `provideAppInitializer`. - **`provideAppInitializer` in a route's providers.** Application initializers are read only from the root injector during bootstrap. Placed in a lazy route, the function is simply never run, which is easy to miss because nothing errors. - **Heavy work.** Environment initializers run synchronously during injector creation; slow code there delays bootstrap or the navigation that creates the route injector. - **Expecting a per-navigation hook.** For per-navigation work, use router guards, resolvers or events, which belong to the router. ## Angular uses it too Angular itself relies on environment initializers to start things eagerly: - `provideBrowserGlobalErrorListeners()` registers one that sets up the window `error` and `unhandledrejection` listeners forwarding to the `ErrorHandler`. - NgModule classes are constructed eagerly the same way: when an injector is built from an NgModule, Angular registers an environment initializer that injects the module class, which is why code in an NgModule constructor ran as soon as that module's injector was created. In standalone code, `provideEnvironmentInitializer` is the explicit replacement for that old habit of putting start-up logic in a module constructor.
- What happens if you mark a provideEnvironmentInitializer function async and it rejects?Angular calls the function and ignores what it returns, so nothing waits for the Promise and its rejection is not treated as a bootstrap failure. At best it surfaces as an unhandled rejection. Work that must finish, or must stop the app on failure, belongs in `provideAppInitializer`.
- Does an environment initializer in a route's providers run on every navigation to that route?No. It runs when the route's environment injector is created, and the router creates that injector once and caches it on the route. It runs again only if that injector is destroyed and later re-created, which happens only with an opt-in router cleanup feature.
saying these in an interview costs you the question
- provideEnvironmentInitializer waits for a returned Promise before continuing
- provideAppInitializer in a lazy route runs when the route loads
- Environment initializers run after application initializers at bootstrap
- A route's environment initializer runs on every navigation to the route
- provideEnvironmentInitializer is only for the root injector