Creating an InjectionToken Configurable Angular Service | Task

Ole Ersoy
Jul - 04  -  4 min

Scenario

We want to provide a GreetingService that can be configured and initialized before it is injected to any other component, directive, or pipe.

We also want the service provided as separate instances to our application module and any lazy loaded modules.

This is the final demo.

Approach

First create a new Angular Stackblitz and create the folder src/app/services.

Greeting Service Configuration

Create src/app/services/greeting-service.config.ts.

export class GreetingServiceConfig {
    public greeting: string = 'Hello';
    public subject: string = 'Angular';
}

Injection Token

Create src/app/services/greeting-service.token.ts.

import { InjectionToken } from '@angular/core';
import { GreetingServiceConfig } from './greeting-service.config';
export const GREETING_SERVICE_CONFIG_INJECTION_TOKEN = 
new InjectionToken<GreetingServiceConfig>(
   'greeting-service_config_injection_token'
);

The InjectionToken instance will be used to inject a GreetingServiceConfig instance into our GreetingService instances.

Greeting Service

Create src/app/services/greeting.service.ts.

import { Inject, Injectable } from '@angular/core';
import { GreetingServiceConfig } from './greeting-service.config';

import { GREETING_SERVICE_CONFIG_INJECTION_TOKEN } from './greeting-service.token';

@Injectable()
export class GreetingService {
  constructor(
    @Inject(GREETING_SERVICE_CONFIG_INJECTION_TOKEN)
    private config: GreetingServiceConfig
  ) {}

  public initialize() {
    this.salutation = `${this.config.greeting} ${this.config.subject}!`;
  }

  public greeting(message: string): string {
    return `${this.config.greeting} ${message}!`;
  }
  public salutation: string;
}

Note that the constructor injects the GreetingServiceConfig via the InjectionToken.

@Inject(GREETING_SERVICE_CONFIG_INJECTION_TOKEN) private config: GreetingServiceConfig

Greeting Service Module

Create src/app/services/greeting-service.module.ts.

import { ModuleWithProviders, NgModule } from '@angular/core';
import { GreetingService } from './greeting.service';
import { GreetingServiceConfig } from './greeting-service.config';

import { GREETING_SERVICE_CONFIG_INJECTION_TOKEN } from './greeting-service.token';

@NgModule({
  providers: [GreetingService],
})
export class GreetingServiceModule {
  constructor(greetingService: GreetingService) {
    greetingService.initialize();
  }

  static config(
    config?: Partial<GreetingServiceConfig>
  ): ModuleWithProviders<GreetingServiceModule> {
    return {
      ngModule: GreetingServiceModule,
      providers: [
        {
          provide: GREETING_SERVICE_CONFIG_INJECTION_TOKEN,
          useValue: Object.assign(new GreetingServiceConfig(), config),
        },
      ],
    };
  }
}

We have created a config(config?: Partial<GreetingServiceConfig>) method with a return type:

ModuleWithProviders<GreetingServiceModule>

We made the config? argument a Partial allowing us to omit configuration parameters that we want to leave as default.

The returned module provides the GreetingServiceConfig instance that will be injected into the GreetingService instance provided by the module.

In addition we also in inject the GreetingService via the GreetingServiceModule constructor and call initialize on the instance, such that the service becomes initialized prior to being injected into clients.

export class GreetingServiceModule {
    constructor(greetingService: GreetingService) {
        greetingService.initialize();
}
...

Application Module

Now that we have completed the GreetingServiceModule we can import and declare a configuration for the GreetingService within the app-module.ts.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { HelloComponent } from './hello.component';
import { GreetingServiceModule } from './services/greeting-service.module';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule,
    GreetingServiceModule.config({ greeting: 'Hola' }),
  ],
  declarations: [AppComponent, HelloComponent],
  bootstrap: [AppComponent],
})
export class AppModule {}

In the above case we are setting the greeting property to Hola.

GreetingServiceModule.config({ greeting: 'Hola' }),

We inject the GreetingService into the salutation we get Hola Angular!.

constructor(g: GreetingService) {
    console.log(g.salutation);
}

This indicates to us that the service was initialized in the GreetingModule constructor prior to its injection into the AppComponent.

Lazy Module

Our demo also contains a lazy loaded module that can be loaded by clicking the Load Lazy button in the AppComponent.

<button routerLink="/lazy">Load Lazy</button>

If we do this we see that when loaded it logs.

Lazy Loaded and the greeting is Ciao Bella!

This indicates that the LazyModule has its own GreetingService instance configured with the greeting property set to Ciao.

@NgModule({
declarations: [LazyComponent],
imports: [
CommonModule,
LazyRoutingModule,
GreetingServiceModule.config({
   greeting: 'Ciao',
}),
],
})
export class LazyModule {}

Demo