Bootstrapping the Application
Every Angular application needs an entry point that mounts the root component into the DOM and wires up the providers the rest of the app depends on. In modern Angular (17/18/19) that entry point is bootstrapApplication — a standalone-first API that replaces the old platformBrowserDynamic().bootstrapModule(AppModule) flow. Understanding how main.ts and app.config.ts fit together is the foundation for everything else: routing, HTTP, hydration, and dependency injection all start here.
The entry point: main.ts
When the Angular CLI builds your project, it uses main.ts as the bootstrap file (configured in angular.json under architect.build.options.main). In a standalone app, main.ts is intentionally tiny. It imports the root component and an application configuration object, then hands both to bootstrapApplication.
// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));
bootstrapApplication returns a Promise<ApplicationRef>. It creates a root injector, instantiates AppComponent, and attaches it to the host element declared in the component’s selector (typically <app-root></app-root> in index.html). The .catch handler surfaces any synchronous bootstrap failures — for example a missing provider — so they don’t get swallowed.
The root component passed to
bootstrapApplicationmust be a standalone component (standalone: true, which is the default in Angular 19). Passing an@NgModule-declared component here throws at runtime.
Structuring providers in app.config.ts
Because there’s no root AppModule in a standalone app, application-wide providers live in an ApplicationConfig object instead. By convention this is exported from app.config.ts. Keeping it in its own file keeps main.ts clean and makes the configuration easy to reuse — the server-side rendering entry point, for example, can merge its own config on top of it.
// src/app/app.config.ts
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withFetch } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(withFetch()),
],
};
The providers array accepts the same provider syntax you’d use anywhere in Angular: provide* configuration functions, class providers, value providers, and factory providers. The provide* functions (like provideRouter and provideHttpClient) are the standalone equivalents of importing feature modules such as RouterModule.forRoot() or HttpClientModule.
Common application providers
| Provider function | Purpose | Package |
|---|---|---|
provideRouter(routes) | Configures the router with top-level routes | @angular/router |
provideHttpClient(withFetch()) | Enables HttpClient using the Fetch API | @angular/common/http |
provideAnimationsAsync() | Lazy-loads the animations engine | @angular/platform-browser/animations/async |
provideClientHydration() | Reuses server-rendered DOM during SSR | @angular/platform-browser |
provideZoneChangeDetection(opts) | Tunes Zone.js change detection | @angular/core |
Adding your own providers
The config is also where you register your own injectable values — environment tokens, interceptors, error handlers, and the like. You can mix provide* helpers with classic provider objects, and functional interceptors register through the router/HTTP helpers.
// src/app/app.config.ts
import { ApplicationConfig, ErrorHandler, InjectionToken } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './core/auth.interceptor';
import { GlobalErrorHandler } from './core/global-error-handler';
export const API_URL = new InjectionToken<string>('API_URL');
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptors([authInterceptor])),
{ provide: API_URL, useValue: 'https://api.example.com' },
{ provide: ErrorHandler, useClass: GlobalErrorHandler },
],
};
Any component or service can then read these values with inject():
import { Component, inject } from '@angular/core';
import { API_URL } from './app.config';
@Component({
selector: 'app-root',
standalone: true,
template: `<p>Calling {{ apiUrl }}</p>`,
})
export class AppComponent {
readonly apiUrl = inject(API_URL);
}
What happens during bootstrap
When bootstrapApplication runs, Angular performs these steps in order, and you can observe the result in the browser console if a provider is misconfigured.
bootstrapApplication(AppComponent, appConfig)
.then(() => console.log('App bootstrapped'))
.catch((err) => console.error('Bootstrap failed:', err));
Output:
App bootstrapped
If, for instance, you use HttpClient without calling provideHttpClient, the bootstrap promise rejects:
Output:
Bootstrap failed: NullInjectorError: No provider for _HttpClient!
Avoid
importProvidersFrom(...)unless a library only ships anNgModule. Prefer the dedicatedprovide*functions — they tree-shake better and are the long-term direction of the framework.
Best Practices
- Keep
main.tsminimal: import the root component andappConfig, callbootstrapApplication, and handle errors with.catch. - Centralize application-wide providers in
app.config.tsso SSR and test setups can reuse or extend them. - Prefer
provide*functions overimportProvidersFromto maximize tree-shaking and stay aligned with modern Angular. - Enable
provideZoneChangeDetection({ eventCoalescing: true })to reduce redundant change-detection cycles. - Register cross-cutting concerns (HTTP interceptors, global error handler) in the config rather than scattering them across components.
- Use
inject()to consume tokens defined in the config instead of constructor injection where it keeps code terser. - Split large configs by feature and compose them, but keep a single exported
appConfigas the source of truth for bootstrap.