Pattern to enable customization through DI

2019-09-12 06:43发布

问题:

I'm looking at the implementation of in-memory-web-api and there is the following code:

@Injectable()
export class InMemoryBackendService {
  protected config: InMemoryBackendConfigArgs = new InMemoryBackendConfig();
            ^^^^^^
  ...

      constructor(
        @Inject(InMemoryBackendConfig) @Optional() config: InMemoryBackendConfigArgs 
                                                   ^^^^^^
        ) {
        ...

As I understand the pattern is the following:

  1. Defined class property and instantiate a dependency without using DI
  2. Optionally inject dependency

If a user provides modified dependency through DI, it will be injected and the default one instantiated without DI will be overridden. I suspect something similar maybe with RequestOptions in HTTP module.

Is this a common pattern?

EDIT:

It turns out that in-memory-web-api is not exactly the pattern I'm asking about. Suppose, I have a class A that uses instance of class B injectable with the token B. So they are both registered with the root injector:

providers: [A, B]

Now, if a user wants to customize B, he can register the customized version under the same token, thus effectively overrriding the original B:

providers: [{provide:B, useClass: extendedB}]`

This is how RequestOptions can be extended in http module.

回答1:

The default value isn't just overridden. The most important part here is

Object.assign(this.config, config || {})

Nothing would happen without it.

This pattern isn't specific to DI, it is a common recipe for default property values, similar to _.defaults.

I would say that InMemoryBackendConfig default implementation is useless abstraction here. Since this.config is always merged with config, the former could be just a plain object

  protected config: InMemoryBackendConfigArgs = { ... };

InMemoryBackendConfig and RequestOptions use complicated variations of this pattern. Yes, in most basic form this is how this can be done:

providers: [{provide:B, useClass: extendedB}]`

This pattern is widely used by constant services in AngularJS for configuration objects, but having B as a class instead of plain object allows to extend the original values instead of replacing them.