5

Today I stumbled upon something that I didn't think would cause me trouble.

In Java and Spring, I can declare two beans that both implement a given interface, while in another class where they are injected I only work with the interface; this is in fact what I love with IoC: you don't really have to know what object you're working with, only it's kind.

So in my little Angular2/Typescript program, I was trying to do the same:

webapp.module.ts:

... 
import { WebAppConfigurationService } from './app/services/webapp.configuration.service';

@NgModule({
  ...
  providers: [WebAppConfigurationService]
})
export class AppModule { }

tnsapp.module.ts:

...
import { TnsConfigurationService } from './services/tns.configuration.service';

@NgModule({
   ...
   providers: [TnsConfigurationService]
})
export class AppModule { }

Both of these modules are using a different provider: TnsConfigurationService or WebAppConfigurationService.

However, these two @Injectable services implement the same interface:

configuration.interface:

export interface IConfigurationService {
    ...
}

Finally, in one of my components, I use the injectable provided by one of these modules I showed you at the beginning:

import { IConfigurationService } from './configuration.interface';

export class HeroesService {

    constructor(private configurationService: IConfigurationService) { }
}

My expectation was that this last component being injected with the right service, even though the parameter is only explicitely defining the interface. Of course I get an error ("Error: Can't resolve all parameters for HeroesService")

Now, I don't expect an easy solution for this as it sounds as an architectural lack. But maybe someone can point me out to an alternative design?

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
Sebas
  • 21,192
  • 9
  • 55
  • 109
  • Take a look at DI documentation https://angular.io/docs/ts/latest/guide/dependency-injection.html#typescript-interfaces-aren-t-valid-tokens – yurzui Feb 23 '17 at 17:33
  • @yurzui I love it: "It's not Angular's fault." :-) Ok then, maybe there´s still a beautiful way to do it... I´ll read on later about this `OpaqueToken` – Sebas Feb 23 '17 at 17:35
  • OpaqueToken is primarily intended for non-class providers (factories and values). – Estus Flask Feb 23 '17 at 17:57

1 Answers1

8

In order for a provider to be injected, it should be registered as a provider. There's no IConfigurationService provider. And it cannot be a provider, because interfaces don't exist in compiled JS code.

The common practice for interfaces that are supposed to be used as provider tokens is to be abstract classes:

abstract class ConfigurationService { ... }

@Injectable()
class WebAppConfigurationService extends ConfigurationService { ... }

...
providers: [{ provide: ConfigurationService, useClass: WebAppConfigurationService }]
...

This recipe is commonly used by Angular 2 itself, e.g. abstract NgLocalization class and concrete NgLocaleLocalization implementation.

Sebas
  • 21,192
  • 9
  • 55
  • 109
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • This looks really good, I'll keep you posted tomorrow morning (UTC) – Sebas Feb 23 '17 at 21:38
  • Is this still up to date for angular 4 / 5? Coming from other languages that embrace DI such as Java I find it to be really strange having to use abstract classes as providers. – Philip Feldmann Oct 28 '17 at 14:24
  • @PhilipFeldmann Yes, still totally relevant. To my knowledge, TS and its interfaces look strange enough to the ones with Java background, so no wonder. – Estus Flask Oct 28 '17 at 15:09