Angular Dependency Injection Token

November 13, 2024 11:08 AM

Angular @Inject decorator Tree-Shakeable provider
We are aware of injecting instances of a class service. But now we are going to see how could we inject a plain javascript object such as app wide configuration object. This is a very common scenario in Angular applications.

For that let's create a file config.ts and place some some interface and data correspond to the interface.

//config.ts
import { InjectionToken } from "@angular/core";


export interface AppConfig {
  apiUrl: string;
  cacheSize: number;
}


export const APP_CONFIG: AppConfig = {
  apiUrl: 'http://localhost:3000',
  cacheSize: 15,
}


// we want to inject this APP_CONFIG plain js object into our Angular application. For that we need to make it injectable by using InjectionToken, as we know everything that gets injected in our Angular App has associated injection token, so we need to create an InjectionToken for our AppConfig object.

// Note: here InjectionToken class needs type of things that is getting injected
export const APP_CONFIG_TOKEN = new InjectionToken<AppConfig>('APP_CONFIG_TOKEN'); // you can pass any unique string here.


Let's add this plain javascript object as a dependency to our App Component class.

In the providers array, as usual we will pass an object with few properties. One of them is provide property asking for Dependency Injection Token so we imported and passed APP_CONFIG_TOKEN.

Another property is useFactory asking for a function which will return the value immediately. Note that here our function has no dependency or we would have to pass it in another property deps:[ ]

@Component({
  selector: "app-root",
  standalone: true,
  imports: [],
  templateUrl: "./app.component.html",
  styleUrl: "./app.component.scss",
  providers: [
    { provide: APP_CONFIG_TOKEN, useFactory: () => APP_CONFIG },
  ],
})
export class AppComponent {
  constructor() { } }


Now let's think about injecting our global configuration object using the  constructor.

export class AppComponent {
  constructor(private config: AppConfig) {
    console.log(this.config);
  }
}


Now if you try this out without further modification, we will see that our app is throwing an error on the browser.

Consider using the @Inject decorator to specify an injection token.

Error: No suitable injection token for parameter 'config' of class 'AppComponent'. Consider using the @Inject decorator to specify an injection token.


This error is indicating that somehow our Angular Dependency System can not find what token it should  associate with this AppConfig object type which is nothing but an interface and will be only computed or exists at compile time construct only  but not runtime like in the case of class.


So we will need @Inject() decorator to specifiy what token we need here for DI.

export class AppComponent {
 
  constructor(@Inject(APP_CONFIG_TOKEN) private config: AppConfig) {
    console.log(this.config); // { apiUrl: 'http://localhost:3000', cacheSize: 15 }
  }
 
}


So with this modification if we check our console. we will be finding the output of config value.


The @Inject() decorator is useful in the cases where we can not use a Class as a Dependency Token, in those cases, we required to specify the token manually using this @Inject() decorator.


We can also use the alternate property that is available here "useValue" instead of "useFactory". So it will directly provide the value whenever this token "APP_CONFIG_TOKEN" gets requested. It will work in the same way.

 providers: [
    // { provide: APP_CONFIG_TOKEN, useValue: { apiUrl: 'http://localhost:3000', cacheSize: 15 } },
    { provide: APP_CONFIG_TOKEN, useValue: APP_CONFIG },
  ],


Let's talk about one important things here.

The Provider which we have written in the App Component providers array is not a Tree Shakeable.  It means even though if you remove the injection from constructor method. This token configuration object is still attached to the application bundle. let's confirm it by removing it.

@Component({
  selector: "app-root",
  standalone: true,
  imports: [],
  templateUrl: "./app.component.html",
  styleUrl: "./app.component.scss",
  providers: [{ provide: APP_CONFIG_TOKEN, useValue: APP_CONFIG }],
})
export class AppComponent {
  constructor() {}
}


We can clearly see that even though we are not using this app config token injection. It has been included in our app main.js file.

image_4.png


However, if you notice that the type of APP_CONFIG which was "AppConfig" interface can not be found here and we are already aware about this part that type or interface are not available in the run-time. It just get computed at a compile time.


Let's make our provider Tree Shakeable

First thing we need to do is to remove our provider configuration object from App Component Provider Array.

@Component({
  selector: "app-root",
  standalone: true,
  imports: [],
  templateUrl: "./app.component.html",
  styleUrl: "./app.component.scss",
  providers: [],
})
export class AppComponent {
  constructor() {}
}

Now we will get back to our config.ts file where we have our App config object and its injection token.

In order to make our provider tree-shakeable we need to pass an extra object as a parameter to the InjectionToken.

This configuration object will have "providedIn" property which will indicate that this  a global injector or singleton, same instance in a whole application wide.

Another property will be a "factory" which wil hold a function or we  can say a factory function and it will return our object data.

export const APP_CONFIG_TOKEN = new InjectionToken<AppConfig>('APP_CONFIG_TOKEN', {
  providedIn: 'root',
  factory: () => APP_CONFIG,
});


With this place, if we reload our application, we will find everything is working as earlier. But this time our provider is tree-shakeable that means if we won't inject this in our components, It won't be included in our application bundle. Let's confirm it. We are still not passing anything in our App Component constructor (not using this service).

WhatsApp Image 2024-11-13 at 11.03.51.jpeg


Let's add back the injection again to confirm if everything is working fine.

//app.component.ts

import { Component, Inject } from"@angular/core";
import { APP_CONFIG, APP_CONFIG_TOKEN, AppConfig } from"./config";
@Component({
  selector:"app-root",
  standalone:true,
  imports: [UserComponent],
  templateUrl:"./app.component.html",
  styleUrl:"./app.component.scss",
  providers: [],
})
exportclassAppComponent {
  constructor(@Inject(APP_CONFIG_TOKEN) privateconfig: AppConfig) {
    console.log(this.config); // { apiUrl: 'http://localhost:3000', cacheSize: 15 }
  }
}



Comments


    Read next