0

I'm trying to use alternative class provider. The best way to describe my problem is through an example. I have this service:

export MyService {
  method() {
    // Some Code
  }
}

And I am extending it with a child service:

export MyAnotherService extends MyService {
  anotherMethod() {
    // Some Code
  }
}

Then providing it in the app module's provider like this:

{ provide: MyService, useClass: MyAnotherService }

Then I'm using the parent service like this:

constructor(
  private service: MyService
) {}

ngOnInit() {
  this.service.method();
  this.service.anotherMethod(); // <- Typescript complains about this line.
}

Everything works fine and the app is running in the browser except I'm getting a TypeScript warning:

[ts] Property 'anotherMethod' does not exist on 'MyService'.

Is it possible to make TypeScript aware of the fact that anotherMethod actually exist?

Note: Accessing anotherMethod like this.service['anotherMethod']() solves the problem but I want to make TypeScript aware of this method.

Sabbir Rahman
  • 1,191
  • 2
  • 13
  • 17
  • 1
    Any reason to not inject `MyAnotherService` and use it? `private service: MyAnotherService` – Aleksey L. May 10 '18 at 11:23
  • Yes. I'm working on a multi app project where `MyService` belongs to a library and internally used there. `MyService` can also be used from any of my applications. Now I need to extends `MyService` to add more feature for one of my application. I might even need to override method from `MyService` which will have effect on the internal behavior of the library itself. Thats why I need to replace `MyService` with my `MyAnotherService` using DI. – Sabbir Rahman May 10 '18 at 11:42
  • The `{ provide: MyService, useClass: MyAnotherService }` construct expects MyService to have exactly the same methods as MyAnotherService does. As this is not the case the compiler complains about it. –  May 10 '18 at 11:50
  • Something wrong with described architecture - if you're explicitly using the extended version (calling its method) I see no reason to declare it as base type – Aleksey L. May 10 '18 at 11:58
  • 1
    @Aleksey L. My library uses the base class and it doesn't have access to the child class. This child class is used by one application. Other application might use the base class directly or have there own extended version. Anyway thanks for the comments. I've solved the issue. – Sabbir Rahman May 10 '18 at 13:47

2 Answers2

1

Solved the problem. I have to use Inject here:

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

Then in constructor:

constructor(
  @Inject(MyService) private service: MyAnotherService
) {}

This way I'll have the instance of MyAnotherService injected by MyService while having the interface of MyAnotherService.

Sabbir Rahman
  • 1,191
  • 2
  • 13
  • 17
  • No, you'll have instance of `MyAnotherService` injected by `MyService` token. Moreover - if you'll remove `{ provide: MyService, useClass: MyAnotherService }` you'll get an error at runtime – Aleksey L. May 10 '18 at 11:59
  • @AlekseyL. I believe this is what OP asked for. This is the correct way to do that, see the explanation in another answer. It's just unclear if it's proper solution, because it's unclear what MyService and MyAnotherService really are and why should they be injected as MyService everywhere. – Estus Flask May 10 '18 at 12:25
  • @estus, I'm just saying that statement _I'll have the instance of MyService while having the interface of MyAnotherService_ is wrong – Aleksey L. May 10 '18 at 12:42
  • 1
    @AlekseyL. Yes, it may be confusing, I guess it should be read as *...the instance of MyService (provider) while...* – Estus Flask May 10 '18 at 12:58
0

This generally can be addressed with Inject, as asker's own answer shows.

constructor(service: MyService) {}

type annotation is syntactic sugar for:

constructor(@Inject(MyService) service: MyService) {}

This is what Injectable decorator is for (as explained in this answer).

So if class token resolves to to another class instance on injection, it can be typed accordingly:

constructor(@Inject(MyService) service: MyAnotherService) {}

This solution may be an indicator of XY problem; it's only acceptable if MyService is stateful, and should be a singleton, and is used as MyService instance in a one place that cannot be modified by developer, but is used as MyAnotherService in another place.

Otherwise two different providers should be declared and used where appropriate:

providers: [MyService, MyAnotherService]
Estus Flask
  • 206,104
  • 70
  • 425
  • 565