Angular 4 Tutorial – Run Code During App Initialization

by | Oct 16, 2017

In my previous post (Deploying Angular 4 Apps with Environment-Specific Info), I used route resolvers to ensure that the settings were pulled into the app before the first component was shown.  In this Angular 4 Tutorial, I hope to show how you can get your settings (or do whatever else) during the Angular app initialization process by using the APP_INITIALIZER injection token.

The code for this Angular 4 Tutorial is located here:  https://github.com/IntertechInc/angular-app-initializer

What is the APP_INITIALIZER injection token?

The APP_INITIALIZER injection token is a way for you to take part in the Angular app initialization process to do your own work.  It is used as a provider in either the AppModule, core module or your own app load module.  Typically you would do things that need to be done before the application is loaded such as loading settings from the server and using those settings to configure your app.  We used it in my current project to get the settings that live in an XML file on the server.  The settings are different based on the environment that the site is deployed to.

For a full (and good) explanation of this token see this post.

Create an AppLoad Module

Although not necessary, it does help to isolate the things you are doing during app load by creating an app load module.

Here is an example:

import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule } from "@angular/common/http";

import { AppLoadService } from './app-load.service';

export function init_app(appLoadService: AppLoadService) {
    return () => appLoadService.initializeApp();
}

export function get_settings(appLoadService: AppLoadService) {
    return () => appLoadService.getSettings();
}

@NgModule({
  imports: [HttpClientModule],
  providers: [
    AppLoadService,
    { provide: APP_INITIALIZER, useFactory: init_app, deps: [AppLoadService], multi: true },
    { provide: APP_INITIALIZER, useFactory: get_settings, deps: [AppLoadService], multi: true }
  ]
})
export class AppLoadModule { }
  • APP_INITIALIZER is imported from @angular/core
  • There can be multiple APP_INITIALIZER injection tokens…they are run concurrently and the initialization process is complete when they all finish
    • Use the “multi: true” option for multiple injection tokens, to restrict to one, use “multi: false”
  • The factory functions (init_app and get_settings) should return a function that returns a promise
  • This module of course must be added to the imports array in the AppModule

Create an AppLoad Service

The AppLoadService will isolate the things you do during app initialization.  Of course this isn’t required and you can use whatever services you need.  This is the implementation of the two methods that were called in the previous code.  It is injected as a dependency in the AppLoadModule providers array.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import 'rxjs/add/operator/toPromise';

import { APP_SETTINGS } from '../settings';

@Injectable()
export class AppLoadService {

  constructor(private httpClient: HttpClient) { }

  initializeApp(): Promise<any> {
    return new Promise((resolve, reject) => {
          console.log(`initializeApp:: inside promise`);

          setTimeout(() => {
            console.log(`initializeApp:: inside setTimeout`);
            // doing something

            resolve();
          }, 3000);
        });
  }

  getSettings(): Promise<any> {
    console.log(`getSettings:: before http.get call`);
    
    const promise = this.httpClient.get('http://private-1ad25-initializeng.apiary-mock.com/settings')
      .toPromise()
      .then(settings => {
        console.log(`Settings from API: `, settings);

        APP_SETTINGS.connectionString = settings[0].value;
        APP_SETTINGS.defaultImageUrl = settings[1].value;

        console.log(`APP_SETTINGS: `, APP_SETTINGS);

        return settings;
      });

    return promise;
  }
}
  • initializeApp is just sitting for three seconds and logging to the console to show that these two methods are run concurrently
  • getSettings is calling a mock API I created using APIARY to get settings
    • It uses those settings to set the values in the APP_SETTINGS object
  • They are both returning Promises

Let’s Run This…

When I run this, I see the following in the console:

  • You can see both methods are being called (initializeApp and getSettings)
  • The settings are returned first
  • initializeApp finishes last and then the application is bootstrapped

How to call API if you need the URL from the settings?

Someone might wonder:  “How can I call an API endpoint if I don’t have my settings to know what the URL is?”  A valid question.  You could always pass a relative URL into the httpClient.get method, which assumes that the API is on the same endpoint as the website.  If you don’t have that luxury, you could define them using the environment files, but that requires a rebuild.  Which you may or may not care about.

Conclusion

The APP_INITIALIZER injection token is a great way to do work during the Angular app initialization process.  You can define multiple so that they can be isolated and run concurrently.  The app isn’t bootstrapped until all of the functions promises resolve.  Try it out for yourself!