DEV Community

Ayyash
Ayyash

Posted on • Updated on

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

If you are anything like me, chances are, you do not have a huge 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

Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

PLATFORM_INITIALIZER

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

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

The first two are provided in AppModule and the third is mysteriously 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 {}

Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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...
*/
Enter fullscreen mode Exit fullscreen mode

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. 👋🏼

Discussion (0)