Sometimes, you need to initialize a service before your angular application starts. Maybe you want to:
- Fetch data from an external API
- Load configuration data
In order to initialize the service when the application bootstraps, you need the use APP_INITIALIZER injection token.
How to use APP_INITIALIZER
- Create a factory function. This is the function that will be executed during the application bootstrap. We'll initialize the service in it.
- Provide the factory function in the AppModule with the APP_INITIALIZER injection token.
Here is a simple example:
export function initSynchronousFactory() {
return () => {
console.log('initSynchronousFactory');
// run initialization code here
};
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { initSynchronousFactory } from './init-synchronous.factory';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule, HttpClientModule],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initSynchronousFactory,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
How to use APP_INITIALIZER with a Promise
As defined in the documentation, if the factory function returns a promise, the application initialization will be blocked until the promise returns.
Note that Observables are not supported yet.
In the following example, the application startup will be blocked for 5 seconds, until the promise is resolved. This means that the AppComponent will be rendered after 5 seconds.
export function initLongRunningFactory() {
return () => {
return new Promise((resolve) => {
console.log('initLongRunningFactory - started');
setTimeout(() => {
console.log('initLongRunningFactory - completed');
resolve();
}, 5000);
});
};
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { initLongRunningFactory } from './init-long-running.factory';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule, HttpClientModule],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initLongRunningFactory,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
How to use APP_INITIALIZER with dependencies
If you want to initialize a service before the application starts, you need to pass an instance of the service to the factory function, via the deps
property of the APP_INITIALIZER provider.
First, let's declare a service that will fetch a quote, asynchronously:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root',
})
export class QuoteOfTheDayService {
constructor(private httpClient: HttpClient) {}
fetchQuote() {
return this.httpClient.get('https://quotes.rest/qod?language=en');
}
}
Next, create a factory function that will use this service. Note that a QuoteOfTheDayService instance is passed as a parameter to the function.
import { QuoteOfTheDayService } from './quote-of-the-day.service';
export function initWithDependencyFactory(
quoteOFTheDayService: QuoteOfTheDayService
) {
return () => {
console.log('initWithDependencyFactory - started');
return quoteOFTheDayService
.fetchQuote()
.toPromise()
.then((result) => {
console.log('result', result);
console.log('initWithDependencyFactory - completed');
});
};
}
Finally, inject the factory function:
import { QuoteOfTheDayService } from './quote-of-the-day.service';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { initWithDependencyFactory } from './init-with-dependency.factory';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule, HttpClientModule],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initWithDependencyFactory,
deps: [QuoteOfTheDayService],
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
How to initialize configuration from external file and initialize a service
Here is a more useful, realistic scenario. You want to use a LoggingService to log debugging information to the console. You want to turn the logging on in the development environment and off in production. One way to do this is to store this information in a configuration file.
We're going to use the APP_INITIALIZER injection token to load the configuration file, retrieve whether the logging should be on or off and initialize the LoggingService
1- Create the configuration file
{
"logging": true
}
2- Create a configuration service
The ConfigurationService
will read the configuration file and store its value.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root',
})
export class ConfigurationService {
logging: boolean;
constructor(private httpClient: HttpClient) {}
async loadConfiguration(): Promise<any> {
const config = await this.httpClient
.get('./assets/config/app.json')
.toPromise();
Object.assign(this, config);
return config;
}
}
3- Create a logging service
The LoggingService
will log information to the console, if the option is turned on. The options is set via the initialize
function.
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class LoggingService {
enabled: boolean;
constructor() {}
initialize(enabled: boolean) {
this.enabled = enabled;
}
log(message: string) {
if (this.enabled) {
console.log(message);
}
}
}
4- Create a factory function that will use the ConfigurationService and LoggingService
import { ConfigurationService } from './configuration.service';
import { LoggingService } from './logging.service';
export function initServicesFactory(
configurationService: ConfigurationService,
loggingService: LoggingService
) {
return async () => {
console.log('initServicesFactory - started');
const config = await configurationService.loadConfiguration();
loggingService.initialize(config.logging);
console.log('initServicesFactory - completed');
};
}
5- Call the factory function when the application bootstraps
This is the glue that will make sure the ConfigurationService and LoggingService are properly initialized when the application starts.
Notice that since ConfigurationService
and LoggingService
are expected parameters of the initServicesFactory
function, they are passed into the deps
array.
import { LoggingService } from './logging.service';
import { ConfigurationService } from './configuration.service';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { initServicesFactory } from './init-services.factory';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule, HttpClientModule],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initServicesFactory,
deps: [ConfigurationService, LoggingService],
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
How to use multiple APP_INITIALIZER
You may want to use multiple initialization factory functions in your application. For example, you could be developing a large application, with many modules (a configuration module, a security module, several feature modules, etc.). It would make sense for each module to run its own initialization factory function, independently from each other.
To run multiple initialization factory functions, you can register many instances of the APP_INITIALIZER provider and set the property multi: true
for each one of them.
All functions will run simultaneously, and the application will wait for all of them to complete(and promises to resolve) before bootstrapping.
Building on the examples above, you can run all initialize function simultaneously like so:
import { LoggingService } from './logging.service';
import { ConfigurationService } from './configuration.service';
import { QuoteOfTheDayService } from './quote-of-the-day.service';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { initLongRunningFactory } from './init-long-running.factory';
import { initWithDependencyFactory } from './init-with-dependency.factory';
import { initServicesFactory } from './init-services.factory';
import { initSynchronousFactory } from './init-synchronous.factory';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule, HttpClientModule],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initSynchronousFactory,
multi: true,
},
{
provide: APP_INITIALIZER,
useFactory: initLongRunningFactory,
multi: true,
},
{
provide: APP_INITIALIZER,
useFactory: initWithDependencyFactory,
deps: [QuoteOfTheDayService],
multi: true,
},
{
provide: APP_INITIALIZER,
useFactory: initServicesFactory,
deps: [ConfigurationService, LoggingService],
multi: true,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}