0

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.

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488

1 Answers1

2

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.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • yeah, thanks, I missed that they don't provide access modifier for `config` in the constructor. So this is different pattern then. The example with `RequestOptions` of `http` module is more to the point. Let me edit the question. – Max Koretskyi Feb 20 '17 at 16:32
  • by the way, what's your linkedin account? – Max Koretskyi Feb 20 '17 at 16:32
  • Actually, I see RequestOptions as a more complicated version of this and quite unique. Instead of `assign`, It does more complex merging with [merge method](https://github.com/angular/angular/blob/master/modules/@angular/http/src/base_request_options.ts#L121). It is a available as [a service](https://github.com/angular/angular/blob/master/modules/@angular/http/src/base_request_options.ts#L219) but used through [another helper function](https://github.com/angular/angular/blob/master/modules/%40angular/http/src/http.ts#L21-L40).. – Estus Flask Feb 20 '17 at 17:05
  • I've updated the answer. Yes, the pattern you've suggested is supposed to work like that. I would personally stick to an injectable class for config objects. Angular built-ins aren't the best examples because they are instantiated directly with `new` as well, this doesn't make the things more simple. – Estus Flask Feb 20 '17 at 17:29