Angular app version attribute: APP_BOOTSTRAP_LISTENER token approach

Marin MuštraMarin Muštra
3 min read

When developing applications, eventually we'll have some versioning to reflect our changes. An indicator, visual or on an HTML element, can be helpful when we have CI/CD flows and want to confirm pushed changes on production.

In this article, we will cover the attribute approach on HTML element. We'll use the bumped version from package.json and add attribute to our app's element like Angular does with the ng-version attribute.

Demo

Image description

The Code

Let's dive right in. To achieve this, we'll need to have access to the app's native element. We could get access to the element from the AppComponent, but there is a nicer way to do this, and that is by using APP_BOOTSTRAP_LISTENER token.

First things first, depending on TS config, we may need to set resolveJsonModule to true so that we are allowed to import needed values from package.json. If your moduleResolution is set to bundler you can skip this step.

// tsconfig.json

{
  "compilerOptions": {
    "resolveJsonModule": true
    // ...
  }
  // ...
}

Then we need to define a provider using APP_BOOTSTRAP_LISTENER token. In callback we'll get the ComponentRef which will be used for setting up our version attribute.

// version.provider.ts

import {
  APP_BOOTSTRAP_LISTENER,
  ComponentRef,
  Provider,
  Renderer2,
} from '@angular/core';

import packageJson from './../package.json';
import { AppComponent } from './app.component';

function versionFactory() {
  return ({ injector, location }: ComponentRef<AppComponent>): void => {
    const renderer = injector.get(Renderer2);
    const element = location.nativeElement;

    renderer.setAttribute(
      element,
      `${packageJson.name}-version`,
      packageJson.version
    );
  };
}

export function provideVersion(): Provider {
  return {
    provide: APP_BOOTSTRAP_LISTENER,
    useFactory: versionFactory,
    multi: true,
  };
}

In the end all we have left is to add a provider to our ApplicationConfig.

// app.config.ts

import { provideVersion } from './config/version.bootstrap';

export const appConfig: ApplicationConfig = {
  providers: [
    provideVersion(),
    // ...
  ],
};

The Token

APP_BOOTSTRAP_LISTENER - A DI token that provides a set of callbacks to be called for every component that is bootstrapped.

During the Angular initialization process - when instantiating the application (and executing loadComponent method), registered listeners under the APP_BOOTSTRAP_LISTENER token will be called and the app component will be passed. At this moment the app component is already attached to DOM and has all the needed services provided. Registered listeners under the APP_BOOTSTRAP_LISTENER token will be called after the NavigationStart event is triggered for the first time of the app's lifetime.

One could ask, when to use the APP_BOOTSTRAP_LISTENER opposed to APP_INITIALIZER for setting various configs in applications? - A General rule of thumb can be based on the need of app component.

Use APP_BOOTSTRAP_LISTENER when you:

  • Need access to app component

  • Potentially need it after child components are added to app component

  • Potentially need it before loading other routed modules/components

Use APP_INITIALIZER when you:

  • Need access to services only

  • Potentially need app bootstrap to wait before sync/async work is done

Conclusion

Having a version attribute on an element is a nice way for the DEV/QA/TS/CS team to follow up on app's changes and have better communication. Also, by using bootstrap tokens, we can neatly "tuck" away all the configs for the application.

If you found this post helpful, feel free to share it with others who might benefit.

Thanks for reading!

0
Subscribe to my newsletter

Read articles from Marin Muštra directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Marin Muštra
Marin Muštra

Lego Builder