Skip to the solution
MediumFrontend9 min read

What is the APP_INITIALIZER token and how is it used to load configuration at startup?

1.0k views

The problem

Question presented to candidate: "We ship one build to staging and production, and the API URL must come from a config.json fetched at startup. How would you make sure that config is loaded before any component or service uses it? Mention APP_INITIALIZER."

What a strong answer should cover:

  • An app initializer is a function Angular runs during bootstrap; if it returns a Promise or Observable, bootstrap waits for it before creating the root component.
  • APP_INITIALIZER is a multi-provider token for such functions; it has been deprecated since v19 in favour of provideAppInitializer(fn), which takes the function directly and runs it in an injection context (so inject(HttpClient) works).
  • The pattern: a ConfigService with a load() method called by the initializer, storing the result; everything else injects ConfigService (or a token derived from it).
  • Multiple initializers run concurrently, not in sequence; if one depends on another, chain them inside one initializer.
  • A rejected Promise fails the bootstrap: the root component is never created, so handle errors (fallback config or a visible error).
  • Trade-off: it delays first render; keep it small, and consider build-time environment files when config does not need to vary per deployment.

Clarifying questions expected:

  • "Does the config differ per deployment of the same build, or per build?"
  • "What should happen if config.json fails to load?"

Code / implementation expected: Yes β€” provideAppInitializer with a config service, and the legacy APP_INITIALIZER form for comparison.

  • bootstrap
  • di

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read Β· 10 sections

Read the code

AngularLoad config.json with provideAppInitializer before the app renders
import { ApplicationConfig, Component, Injectable, inject, provideAppInitializer, signal } from "@angular/core";
import { HttpClient, HttpInterceptorFn, HttpResponse, provideHttpClient, withInterceptors } from "@angular/common/http";
import { delay, firstValueFrom, of, tap } from "rxjs";

const t0 = Date.now();
const log = (msg: string) => console.log(`${String(Date.now() - t0).padStart(4)}ms  ${msg}`);

export interface AppSettings {
  apiUrl: string;
  features: string[];
}

// Holds the loaded config. Everything else injects this, not HttpClient.
@Injectable({ providedIn: "root" })
export class ConfigService {
  private readonly settings = signal<AppSettings | null>(null);
  get value(): AppSettings {
    const s = this.settings();
    if (!s) throw new Error("Config read before it was loaded");
    return s;
  }
  load() {
    log("initializer: GET /assets/config.json");
    return firstValueFrom(
      inject(HttpClient).get<AppSettings>("/assets/config.json").pipe(
        tap((s) => {
          this.settings.set(s);
          log("initializer: config stored");
        }),
      ),
    );
  }
}

// Stand-in for a real server: answers /assets/config.json after 200 ms.
const fakeBackend: HttpInterceptorFn = (req, next) =>
  req.url === "/assets/config.json"
    ? of(new HttpResponse({ status: 200, body: { apiUrl: "https://api.example.com", features: ["beta-search"] } })).pipe(delay(200))
    : next(req);

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptors([fakeBackend])),
    // Runs in an injection context; bootstrap waits for the returned Promise.
    provideAppInitializer(() => inject(ConfigService).load()),
    // A second initializer runs alongside the first one, not after it.
    provideAppInitializer(() => {
      log("initializer 2: start (sync part)");
      return new Promise<void>((done) => setTimeout(() => { log("initializer 2: done"); done(); }, 50));
    }),
  ],
};

@Component({
  selector: "app-root",
  template: `<p>API: {{ api }}</p><p>features: {{ features }}</p>`,
})
export class AppComponent {
  private config = inject(ConfigService);
  api = this.config.value.apiUrl; // safe: the initializer finished before this constructor ran
  features = this.config.value.features.join(", ");
  constructor() {
    log("AppComponent constructor");
  }
}
AngularMigrating an APP_INITIALIZER multi provider to provideAppInitializer
import { APP_INITIALIZER, ApplicationConfig, Component, Injectable, inject, provideAppInitializer } from "@angular/core";

@Injectable({ providedIn: "root" })
export class FeatureFlags {
  flags: string[] = [];
}

const loadFlags = () => new Promise<string[]>((resolve) => setTimeout(() => resolve(["beta-search"]), 30));

export const appConfig: ApplicationConfig = {
  providers: [
    // BEFORE (deprecated since v19): a multi provider whose factory RETURNS the initializer.
    {
      provide: APP_INITIALIZER,
      multi: true,
      deps: [FeatureFlags],
      useFactory: (ff: FeatureFlags) => () =>
        loadFlags().then((f) => {
          ff.flags = f;
          console.log("legacy APP_INITIALIZER: flags loaded");
        }),
    },
    // AFTER: pass the initializer itself. It runs in an injection context, so inject() just works.
    provideAppInitializer(() => {
      const ff = inject(FeatureFlags);
      console.log(`provideAppInitializer: runs too, flags so far = ${ff.flags.length}`);
    }),
  ],
};

@Component({ selector: "app-root", template: `<p>flags: {{ flags.join(", ") }}</p>` })
export class AppComponent {
  flags = inject(FeatureFlags).flags;
  constructor() {
    console.log("AppComponent constructor");
  }
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 20 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextMedium

How do functional interceptors work with provideHttpClient()?

Continue