Introduction to dependency injection in SAP Spartacus

Time:2021-10-14

Let’s first understand the in angularDependency injection

A dependency is a service or object that a class needs to perform its functions. Dependency injection (DI) is a design pattern in which a class requests dependencies from an external source instead of letting the classDo it yourselfCreate them.
Angular’s Di framework provides dependencies for a class when it is instantiated. You can use angular Di to improve the flexibility and modularity of your application.

How to create a new service that can be injected?

Command line: ng generate service heroes / hero

For automatically generated code, note the annotation @ injectable:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class HeroService {
  constructor() { }
}

@The injectable () decorator specifies that angular can use this class in the di system. Metadata providedin: ‘root’ means that heroservice is visible in the whole application.

Configuration provider

By configuring providers, you can provide services to those application components that need them.

The dependency provider will use the di token to configure the injector, which will use it to provide a specific, runtime version of the dependency value.

If you specify a service class as a provider token, the default behavior of the injector is to instantiate that class with new.

In the following example, the logger class provides an instance of the logger.

providers: [Logger]

When a provider is used to configure an injector, it is associated with a dependency injection token (or di token). The injector allows angular to create a mapping of any internal dependencies. The di token acts as the key name for the mapping.

When you use the type of heroservice class to define constructor parameters, angular will inject the service associated with this heroservice class token:

constructor(heroService: HeroService)

Here, the constructor parameter heroservice is actually a token.

This configuration:

providers: [Logger]

It’s actually short for the following:

[{ provide: Logger, useClass: Logger }]
  • The provide attribute stores the token, which is used as a key when locating the dependency value and configuring the injector.
  • The second property is a provider definition object that tells the injector how to create dependency values. The key in the provider definition object can be useclass — just like in this example. It can also be useexisting, usevalue, or usefactory. Each key is used to provide a different type of dependency.

Different classes can provide the same service. For example, the following code tells the injector to return a better logger when the component requests a logger using the logger token

In this way, when our application tries to inject the logger using the following code in the constructor:

constructor(logger: Logger)

What we get at runtime is the betterlogger instance.

How to use the injection token

  1. Use typescript interface to define an AppConfig type (omitted)
  2. Create a constant based on the AppConfig type:
export const HERO_DI_CONFIG: AppConfig = {
  apiEndpoint: 'api.heroes.com',
  title: 'Dependency Injection'
};
  1. To provide and inject a configuration object, specify it in the providers array of @ ngmodule().
providers: [
{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }
],

The provide attribute app in the above code_ Config is a token. We also need to use code to create this token.

import { InjectionToken } from '@angular/core';

export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');

AppConfig is passed into the token constructor through type parameters. App.config is the token description information.

  1. The last step is to inject the token with @ inject in the constructor of the application. At runtime, the value of config is the constant associated with the token: hero_ DI_ CONFIG
constructor(@Inject(APP_CONFIG) config: AppConfig) {
  this.title = config.title;
}

Why can’t we directly take the AppConfig created with the interface in the first step as the value of the provide, and take a lot of trouble to create an injectiontoken?

Although typescript’s AppConfig interface can provide type support in classes, it has no role in dependency injection. In typescript, an interface is a design time artifact that has no runtime representation or token available to the di framework.
When the translator converts typescript to JavaScript, the interface disappears because JavaScript has no interface.
Since angular does not have an interface at runtime, it cannot be used as a token or injected.

Therefore, the following codes do not work:

// Can't use interface as provider token
[{ provide: AppConfig, useValue: HERO_DI_CONFIG })]

See an example of using the injection token in SAP Spartacus:

Introduction to dependency injection in SAP Spartacus

export const PAGE_LAYOUT_HANDLER = new InjectionToken<PageLayoutHandler[]>(
  'PageLayoutHandler'
);

This token is used in cart.module.ts:

Introduction to dependency injection in SAP Spartacus

Finally, the service instance pointed to by this token is used in pagelayoutservice: an array composed of pagelayouthandler.

Introduction to dependency injection in SAP Spartacus

More Jerry’s original articles are: “Wang Zixi”:
Introduction to dependency injection in SAP Spartacus