How to handle async providers in Angular
An elegant solution to a common problem.
Loading a configuration at the start of an Angular app is a common task. Traditionally, you would utilize APP_INITIALIZER
to accomplish this (Angular InDepth has a great piece on this). This works great most of the time.
There are, however, a few situations where this is suboptimal. For example when you are using an internal Component Library that has too much logic (๐) and already has APP_INITIALIZERS
hooks setup. This can cause issues where the execution order of all the hooks is not how you need it to be.
Well, how do we solve this?
There is actually a really easy solution to this - and I don't think a lot of people know about it.
You probably know that every Angular project has a file called main.ts
that usually looks something like this:
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch(err => console.error(err));
Notice the call to platformBrowserDynamic
? If you check the official docs for this you can see that this function actually takes an array of providers as a parameter. Perfect!
With that in mind, the solution looks like this:
- Load whatever you need in the
main.ts
file using the nativefetch
API - Provide the value before the application is bootstrapped
- ???
- Profit
Now, your main.ts
could look like this:
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { BASE_API_URL_TOKEN } from './app/injectors';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
async function main() {
try {
const data = await fetch('/assets/config.json');
const config = await data.json();
await platformBrowserDynamic([
{ provide: BASE_API_URL_TOKEN, useValue: config.baseUrl },
]).bootstrapModule(AppModule);
} catch (error) {
console.error(error);
}
}
main();
And that's it. You can checkout the example repo on GitHub
Conslusion
Always read the docs!
Cheers!