13

I am building an application with Angular (6.0.7) and I am trying to create a service with the new:

@Injectable({
  providedIn: 'root'
})

But how can I type an injection with an Interface?


The problem

I have 2 services, Authentication.service and SessionStorage.service. I want to inject the sessionstorage into the authentication service. That can be done via:

constructor(private sessionStorage: SessionStorage) {
}

No problem there. But for Object Orientated purposes I want to have an interface above this service (so that I can implement both localstorage service as sessionstorage service). Thus it is only logical that I want to type the injected class with the interface, but this cannot be done the same way Angular 5 and lower does it.

So how can I type the injection into this global service with my interface?


I've tried

The Angular service typings describe an InjectableProvider, but this does not match any of the parameters of the siblings of InjectableProvider, so this gives a compiler (and tslint) error.

@Injectable({
  providedIn: 'root'
}, {provide: IStorageService, useClass: SessionStorage})
8
  • I don't understand the purpose of that. What are you trying to achieve ? Commented Jul 4, 2018 at 13:43
  • 3
    Dependency injection like it should be, the class uses an interface and doesn't care about the implementation of the code. Sure Angular works a bit differend with the need to type the injection with 'useValue' everywhere, but it serves the same purpose. Commented Jul 4, 2018 at 13:47
  • And why don't you use the interface with export class MyService implements MyInterface {...} ? Commented Jul 4, 2018 at 13:48
  • And BTW, useValue requires a value (an actual instance of a class), and useClass allows you to use classes. So try with useClass, and maybe useInterface (but this one I'm not sure) Commented Jul 4, 2018 at 13:49
  • @trichetriche, unfortunately the useClass does not work either, since it does not match the parameter of the InjectableProvider (updated answer). About 'why don't you use (..) implements (..)', I don't want to do that because that way I still have to set the exact type in the constructor and I want to avoid that if possible Commented Jul 4, 2018 at 14:03

4 Answers 4

14

This can be done with InjectionToken, which is a replacement for the obsolete OpaqueToken

export const AuthenticationProvider = new InjectionToken(
  "AuthenticationProvider",
  { providedIn: "root", factory: () => new CognitoAuthenticationProvider() }
);

...

@Injectable()
export class CognitoAuthenticationProvider implements IAuthenticationProvider {

...

@Injectable({
  providedIn: "root"
})
export class AuthenticationService {
  constructor(
    @Inject(AuthenticationProvider)
    private authenticationProvider: IAuthenticationProvider,
    private http: HttpClient
  ) {}
Sign up to request clarification or add additional context in comments.

7 Comments

I think this is the best option, unfortunately it needs yet an other variable and the provideIn needs to be defined on the token, where I would expect it to be defined on the service.
@Mr.wiseguy the providedIn is new in ng 6 and is recommended for some tree-shaking optimisation purpose. you can still register providers in the modules themselves.
what if we would like to inject something into 'CognitoAuthenticationProvider' constructor?
I pay attention on the interface IAuthenticationProvider name. Angular style guide says: Consider naming an interface without an I prefix.
@tsiro Also keep in mind the factory parameter is optional, and only provides a default for the injection token. You might be in a case where you don't have a default to provide, in which case you shouldn't use the factory parameter at all. This would let angular fail with an error if you hadn't configured the injector to have any services to inject with that token elsewhere. You can setup the provider when adding your service to a module to use providers: [{ provide: YOUR_TOKEN, useClass: YourConcreteClass }]
|
8

I used something like the following to solve this

app.module.ts

providers: [
  { provide: AlmostInterface, useClass: environment.concrete }
  ...
]

AlmostInterface.ts

export abstract class AlmostInterface {
   abstract myMethod();
}

MyConcrete.ts

export class MyConcrete implements AlmostInterface {
   myMethod() { ... }; // implementation
}

export class MyConcreteAlternative implements AlmostInterface {
   myMethod() { ... }; // implementation
}

environment.ts

export const environment = {
  production: false,
  concrete: MyConcreteAlternative
};

environment.prod.ts

export const environment = {
  production: true,
  concrete: MyConcrete
};

1 Comment

This works, but the problem is that this uses the old Angular 5 syntax for declaring a service. I am asking for a solution with the new Angular 6 syntax, since this (provideIn: 'root') is not declared via the module, but is only injected in the components that use it (better for tree shaking).
2

I think you can't use typescript interfaces for dependency injection as typescript interfaces don't exist at runtime (only for typesafety at compile time).
I would suggest using an abstract class for it.

EDIT: It seems you can use useClass in the first parameter of @Injectable, not as a second like your example. Combining that with @k0zakinio's answer results in:

@Injectable({
  providedIn: 'root',
  useClass: environment.concrete,
  deps: []
})
export abstract class SessionStorage { }

It also seems you need to declare your dependencies via deps or inject, checkout this github issue. I hope this time my answer is of more help.

2 Comments

Well, Angular (and Typescript) does provide a way for dependency injection, see @k0zakinio's answer. So it is possible. But I want to know if it's also possible for the Angular 6 way of services.
Sorry, I totally misunderstood your question.
-1

You can use something like -

[{ provide: InterfaceName, useClass: ClassName}]

1 Comment

This does not match the params defined by the InjectableProvider, so unfortunately this does not work

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.