Sekrab Garage

Angular Initialization tokens

The mysterious three tokens of Angular: APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, PLATFORM_INITIALIZER

AngularTip March 5, 22

If you are anything like me, chances are, you do not have a wide attention span, and you keep your memory space for more important things than to know what each of these tokens mean or how they work! So I setout to have a quick test to see which gets fired first, and what each expects. Through trial and error, and with zero regards to what goes under the hood.

Here is the StackBlitz project.

The documentation

Sucks! But here is what we are fishing for:

APP_BOOTSTRAP_LISTENER

const APP_BOOTSTRAP_LISTENER: InjectionToken<((compRef: ComponentRef<any>) => void)[]>;

// we need to write a function that returns a function with this signature:
(compRef: ComponentRef<any>) => void

APP_INITIALIZER

const APP_INITIALIZER: InjectionToken<readonly (() => void | Observable<unknown> | Promise<unknown>)[]>;

// we need to write a function that returns a function with this signature (I choose observable):
() => Observable<any>

PLATFORM_INITIALIZER

const PLATFORM_INITIALIZER: InjectionToken<(() => void)[]>;

// we need to write a function that returns a function with this signature 
() => void

The first two are provided in AppModule and the third is mystically provided in platformBrowserDynamic, don't know why, don't care. The tip was found buried in stackoverflow

Adding the tokens

Adding the tokens first, then writing the functions later, so that I don't lose my sanity. In AppModule:

@NgModule({
  imports: [BrowserModule, HttpClientModule, CommonModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent],
  providers: [
    {
      provide: APP_BOOTSTRAP_LISTENER,
      useFactory: bootstrapFactory, // will be created
      multi: true,
      deps: [InitService], // will be created to see if it works
    },
    {
      provide: APP_INITIALIZER,
      useFactory: initFactory, // will be created
      multi: true,
      deps: [InitService], // will be created to see if it works
    },
  ],
})
export class AppModule {}

In main.ts as an argument to platformBrowserDynamic

platformBrowserDynamic([
  {
    provide: PLATFORM_INITIALIZER,
    useValue: platformInitFactory, // will be created
    multi: true,
    deps: [InitService] // will be created to see if it works
  },
])
  .bootstrapModule(AppModule)
  .catch((err) => console.error(err));

The factories

Find them in /services/initialize.service.ts file in Stackblitz above. The factories are simple functions that return functions with matching signatures, and sweet ol' console.log for each step. Let's see which gets called first, and what it returns. Let me also inject a dummy service to see if it is at all usable:

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

export const initFactory = (init: InitService): (() => Observable<any>) => {
  console.log('APP_INITIALIZER factory called', init);
  return () => {
    console.log('APP_INITIALIZER callback');
    return of(true);
  };
};

export const platformInitFactory = (a: InitService): (() => void) => {
  console.log('PLATFORM_INITIALIZER factory called', a);
  return () => {
    console.log('PLATFORM_INITIALIZER callback');
  };
};

export const bootstrapFactory = (x: InitService): ((c: ComponentRef<any>) => void) => {
  console.log('APP_BOOTSTRAP_LISTENER factory called', x);
  return (c: ComponentRef<any>) => {
    console.log('APP_BOOTSTRAP_LISTENER Callback:', c.location.nativeElement);
  };
};

@Injectable({
  providedIn: 'root',
})
export class InitService {
 // nothing yet, I want to see how I can utilize this
}

Running this in StackBlitz gets me the following

/*
PLATFORM_INITIALIZER factory called undefined
APP_INITIALIZER factory called InitService {}
APP_INITIALIZER callback
APP_BOOTSTRAP_LISTENER factory called InitService {}
APP_BOOTSTRAP_LISTENER Callback: HTMLElement...
*/

So,

  • PLATFORM_INITIALIZER gets called first but has no visibility for dependencies
  • The PLATFORM_INITIALIZER call back function was never called! I shall unleash my wrath upon it
  • APP_INITIALIZER is fired next, it has access to dependencies, and it allows an asynchronous response
  • APP_BOOTSTRAP_LISTENER is garnish, accesses both dependencies and the app component, I cannot think of a use case for it.
  • Angular docs of these tokens pretty much sucks--but otherwise, they are awesome, don't hate me please!

Use cases

The most useful one is obviously APP_INITIALIZER, I can inject HTTP Client and load something from server upon first initialization, say, configurations?

The second use case I can think of is inject actual JS file from the index.html and map it internally to a typescript interface.

I will attempt to do both, on a different post. 👋🏼