📌 20 de Outubro, 2024
Angular: Run Multiple APP_INITIALIZER sequentially
Informática · Programação
In Angular the APP_INITIALIZER
is designed for running asynchronous code during the application’s initialization phase – it waits for an observable or promise to complete before running code from your main app component. While this feature is quite useful, adding more than one APP_INITIALIZER
will cause them to run in parallel, which makes it difficult to manage dependencies between them.
The Problem
Let’s consider we’ve three different services ConfigService
, LoggingService
and AppAnalyticsService
:
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class ConfigService implements SeqInitInterface {
init(): Observable<any> {
console.log('Initializing ConfigService...');
return of(true); // Replace with your initialization logic
}
}
@Injectable({
providedIn: 'root',
})
export class LoggingService implements SeqInitInterface {
init(): Observable<any> {
console.log('Initializing LoggingService...');
return of(true); // Replace with your initialization logic
}
}
@Injectable({
providedIn: 'root',
})
export class AppAnalyticsService implements SeqInitInterface {
init(): Observable<any> {
console.log('Initializing AppAnalyticsService...');
return of(true); // Replace with your initialization logic
}
}
Let’s say we need to initialize all of those before the app runs anything. One way to do it is to add multiple APP_INITIALIZER
declarations to the providers
.
However, if AppAnalyticsService
depends on settings loaded by ConfigService
, this approach will fail. Angular will initialize AppAnalyticsService
at the same time as ConfigService
, meaning that the settings won’t be available when AppAnalyticsService
‘s initialization method runs.
Solution: Sequential Initialization Service
In order to avoid this pitfall we can implement a SequentialInitializerService
, a service that will be responsible for loading up our services in a controlled and sequential way so our dependencies are predictable:
import { Injectable, Injector } from '@angular/core';
import { Observable, concatMap, of, from } from 'rxjs';
export interface SeqInitInterface {
init(): Observable<any>;
}
@Injectable({
providedIn: 'root',
})
export class SequentialInitializerService {
constructor(private injector: Injector) {}
// This will run each service sequentially and return a promise
initialize(sequentialInitializers: any[]): Promise<any> {
return new Promise((resolve, reject) => {
from(sequentialInitializers)
.pipe(
concatMap(service => {
const serviceInstance = this.injector.get(service);
console.log(`Initializing ${service.constructor.name}...`);
return serviceInstance.init(); // Each init returns an Observable
})
)
.subscribe({
complete: () => {
console.log('All services initialized.');
resolve(true);
},
error: (err) => {
console.error('Error during initialization:', err);
reject(err);
}
});
});
}
}
Now in our main.ts
file we may load it like this:
export const appInitializers = [
ConfigService,
LoggingService,
AppAnalyticsService
];
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(AppRoutingModule, BrowserModule, provideHttpClient(withInterceptorsFromDi()),
{
provide: APP_INITIALIZER,
useFactory: (initService: SequentialInitializerService): () => initService.initialize(appInitializers);
deps: [SequentialInitializerService],
multi: true,
}
]
}).catch(err => console.error(err));
That’s it! A simple and scalable solution for the problem.