Configure your Angular apps with an injection token

In my previous story I described the concept of dependency injection in Angular through the metaphor of ordering coffee in a cafe.

Players of dependency injection in Angular: provider, injector and requesting component

Today we are going to have a look at a practical use case — how you can configure your apps with InjectionToken.

Use Case

Let’s say, you have a monorepo that contains 2 apps and 1 library: RedApp handles all red products and BlueApp handles all blue products. The SharedLibrary provides some functionality which is used by both apps. Let’s further assume that you want to implement a method that prints out product description with the respective general terms and conditions. Since both apps use this information, you can put it in a shared component in your lib.

Straightforward solution

The straightforward solution could look like this.

//color.service.ts (SharedLibrary)public getProductInfo(product: Product){
let termsAndConditionsUrl;
if (product.color === ‘red’)
{ termsAndConditionsUrl = ‘www.red-products.com’; }
else
{ termsAndConditionsUrl = ‘www.blue-products.com’; }
return `Please visit
${termsAndConditionsUrl} for more information about our
${product.color} product`;
}

This solutions does what it should — depending on the product type we will get “Please visit www.red-products.com for more information about our red product” or “Please visit www.blue-products.com for more information about our blue product”. Its drawback, however, is its inability to scale. If you add green products, you need to change this code and in the worst case even retest all apps functionality. It would be better if each app (or product line) kept its own business logic for itself.

Injection token as configuration token

To configure our apps, we will create an additional file config.ts (you can name it as you like) in our shared lib.

//config.tsexport const COLOR_CONFIG_TOKEN = new InjectionToken<ColorConfig>(‘config’);export interface ColorConfig {
productColor: string;
productGeneralConditions: string;
}

In this file we define our new injection token which we going to use to setup our apps. It is called COLOR_CONFIG_TOKEN and requires the interface of ColorConfig.

Now, each app can define its own configuration values in app.module (e.g. in RedApp):

//app.module.ts (red-app)export const APP_CONFIG: ColorConfig = {
productColor: ‘red’,
productGeneralConditions: ‘www.red-product.com’

};@NgModule({

providers: [{ provide: COLOR_CONFIG_TOKEN, useValue: APP_CONFIG }],

})//app.module.ts (blue-app)export const APP_CONFIG: ColorConfig = {
productColor: ‘blue’,
productGeneralConditions: ‘www.blue-articles.com’

};@NgModule({

providers: [{ provide: COLOR_CONFIG_TOKEN, useValue: APP_CONFIG }],

})

With this information, our shared component doesn’t have to care about any colours. It just get’s it via the configuration. You need to inject the token and off you go — getProductInfo() now looks like this:

//color.service.ts (SharedLibrary)public constructor(
@Inject(COLOR_CONFIG_TOKEN) private config: ColorConfig,
) {}public getProductInfo(){
return `Please visit
${this.config.productGeneralConditions}
for more information about our
${this.config.productColor} product`;
}

If the company adds a green product, you will just create a new app and configure it accordingly. No refactoring of the shared service, no regression testing needed. Voilá!

Configuring methods

Let’s make our case a little bit more complex. Let’s assume, our products have the same baseline costs (e.g. fixed costs), but different pricing strategies. The final price for red products is 1,5 times higher than the baseline price, while blue products should get a seasonal discount of 5%. I know this is a weird pricing strategy, but please bear with me, this is a MVE — “minimal viable example”. Now we want to display the following information in our shared component: “Price per unit: 7 USD. Please visit www.red-products.com for more information about our red product” and “Price per unit: 5 USD. Please visit www.blue-products.com for more information about our blue product”.

So we need to extend our shared service to be able to calculate the price per product type. Do you see any problem there? Our method getProductInfo() is part of the shared library and does not care about red or blue products. How should it then decide, which calculation method to use? Actually, it shouldn’t. Red and Blue apps can provide their methods in the config token.

Our config.ts can be extended like this:

//config.tsexport interface ColorConfig {
productColor: string;
productGeneralConditions: string;
getPrice(baselinePrice: number): number;
}

Now we just have to implement it in app.module:

//app.module.ts (red)const getRedPrice = (price: number) => 1.5*price;export const APP_CONFIG: ColorConfig = {
productColor: ‘red’,
productGeneralConditions: ‘www.red-product.com’,
getPrice: getRedPrice,
};@NgModule({

providers: [{ provide: COLOR_CONFIG_TOKEN, useValue: APP_CONFIG }],

})//app.module.ts (blue)const getBluePrice = (price: number) => 0.95*price;export const APP_CONFIG: ColorConfig = {
productColor: ‘blue’,
productGeneralConditions: ‘www.blue-articles.com’,
getPrice: getBluePrice,
};@NgModule({

providers: [{ provide: COLOR_CONFIG_TOKEN, useValue: APP_CONFIG }],

})

Let’s have a look at the shared library again. It knows the baseline price and can now calculate the final price for any type of product by calling getPrice() method provided in the config token.

//color.service.ts (SharedLibrary)const BASELINE_PRICE = 1;public constructor(
@Inject(COLOR_CONFIG_TOKEN) private config: ColorConfig,
) {}public getProductInfo(){
return `Price per unit: ${this.config.getPrice(BASELINE_PRICE)}.
Please visit
${this.config.productGeneralConditions}
for more information about our
${this.config.productColor} product`;
}

To sum up — use InjectionToken to inject app configuration into your shared services and components. This approach enables loosely-coupled implementation and easy scaling.

Code examples from this story can be found here: https://github.com/korneevamg/blog-configuration-with-injection-token

[Disclaimer: did I miss something / is something not quite correct? Please let me and other readers know AND provide missing/relevant/correct information in your comments — help other readers (and the author) to get it straight!]

Configure your Angular apps with an injection token was originally published in ngconf on Medium, where people are continuing the conversation by highlighting and responding to this story.

Leave a Comment

Your email address will not be published. Required fields are marked *