78 votes

Est-il possible d'injecter une interface avec angular2 ?

Je me demande s'il existe un moyen approprié d'injecter des interfaces dans Angular2 ? (cf. ci-dessous)

Je pense que cela est lié à l'absence du décorateur @Injectable() sur l'interface, mais il semble que cela ne soit pas autorisé.

Regards.

Lorsque CoursesServiceInterface est implémenté en tant qu'interface, le compilateur TypeScript se plaint de "CoursesServiceInterface cannot find name" :

import {CoursesServiceInterface} from './CoursesService.interface';
import {CoursesService} from './CoursesService.service';
import {CoursesServiceMock} from './CoursesServiceMock.service';
bootstrap(AppComponent, [
  ROUTER_PROVIDERS, 
  GlobalService,
  provide(CoursesServiceInterface, { useClass: CoursesServiceMock })
  ]);

mais avec CoursesServiceInterface comme interface :

import {Injectable} from 'angular2/core';
import {Course} from './Course.class';
//@Injectable()
export interface CoursesServiceInterface {
    getAllCourses(): Promise<Course[]>;//{ return null; };
    getCourse(id: number): Promise<Course>;// { return null; };
    remove(id: number): Promise<{}>;// { return null; };
}

Lorsque le service est une classe, le compilateur TypeScript ne se plaint plus :

import {Injectable} from 'angular2/core';
import {Course} from './Course.class';
@Injectable()
export class CoursesServiceInterface {  
    getAllCourses() : Promise<Course[]> { return null; };
    getCourse(id: number) :Promise<Course> { return null; };
    remove (id: number) : Promise<{}> { return null; };
}

112voto

Günter Zöchbauer Points 21340

Non, les interfaces ne sont pas supportées pour DI. Avec TypeScript, les interfaces ne sont plus disponibles au moment de l'exécution, mais uniquement de manière statique et ne peuvent donc pas être utilisées comme jetons DI.

Vous pouvez également utiliser des chaînes de caractères comme clés ou InjectionToken

provide('CoursesServiceInterface', {useClass: CoursesServiceMock}) // old

providers: [{provide: 'CoursesServiceInterface', useClass: CoursesServiceMock}]

et l'injecter comme

constructor(@Inject('CoursesServiceInterface') private coursesService:CoursesServiceInterface) {}

Voir aussi https://angular.io/api/core/InjectionToken

0 votes

J'aime cette solution, merci.

84voto

Tobi Points 389

La raison pour laquelle vous ne pouvez pas utiliser les interfaces est qu'une interface est un artefact de conception TypeScript. JavaScript n'a pas d'interfaces. L'interface TypeScript disparaît du JavaScript généré. Il n'y a plus d'informations sur le type d'interface qu'Angular peut trouver au moment de l'exécution.


Solution 1 :

La solution la plus simple consiste à définir une classe abstraite qui implémente l'interface. Souvent, vous avez besoin d'une classe abstraite de toute façon.

Interface :

import {Role} from "../../model/role";

export interface ProcessEngine {

     login(username: string, password: string):string;

     getRoles(): Role[];
}

Classe abstraite :

import {ProcessEngine} from "./process-engine.interface";

export abstract class ProcessEngineService implements ProcessEngine {

    abstract login(username: string, password: string): string;

    abstract getRoles(): Role[];

}

Classe de béton :

import { Injectable } from '@angular/core';
import {ProcessEngineService} from "./process-engine.service";

@Injectable()
export class WebRatioEngineService extends ProcessEngineService {

    login(username: string, password: string) : string {...}

    getRoles(): Role[] {...}

}

Vous pouvez maintenant définir votre fournisseur comme d'habitude :

@NgModule({
      ...
      providers: [
        ...,
        {provide: ProcessEngineService, useClass: WebRatioEngineService}
      ]
})

Solution 2 :

La documentation officielle d'Angular suggère d'utiliser la balise InjectionToken similaire à OpaqueToken. Voici l'exemple :

Votre interface et votre classe :

export interface AppConfig {
   apiEndpoint: string;
   title: string;
}

export const HERO_DI_CONFIG: AppConfig = {
  apiEndpoint: 'api.heroes.com',
  title: 'Dependency Injection'
};

Définissez votre Token :

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

export let APP_CONFIG = new InjectionToken<AppConfig>('app.config');

Enregistrez le fournisseur de dépendances en utilisant l'objet InjectionToken, par exemple dans votre app.module.ts :

providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]

Vous pouvez ensuite injecter l'objet de configuration dans tout constructeur qui en a besoin, à l'aide d'un décorateur @Inject :

constructor(@Inject(APP_CONFIG) config: AppConfig) {
     this.title = config.title;
}

4 votes

Pour ceux qui visent Angular 4 et qui ont trouvé cette réponse, la solution 2 est définitivement la voie à suivre. Avec cette configuration, il est possible d'injecter n'importe quel type de classes fantaisie pour les tests unitaires et autres, en modifiant simplement l'attribut providers à quelque chose comme providers: [{ provide: APP_CONFIG, useClass: AppConfigMockClass }]

1 votes

C'est génial. Remarque rapide : si vous définissez un jeton dans le même fichier que l'interface, vous risquez d'obtenir des avertissements inutiles : github.com/angular/angular-cli/issues/2034

0 votes

@Weikardzaena Pour les tests, changez-vous les fournisseurs dans l'AppModule actuel, ou peuvent-ils être définis dans les tests eux-mêmes (ou en utilisant l'environnement, par exemple test, dev, prod) ?

5voto

San Jaisy Points 2101

Solution alternative pour l'angle 9

@Injectable()
export class TodoListPublicService implements TodoListService {
  getTodos(): Todo[] {
    const todos: Todo[] = [
      {
        title: 'get groceries',
        description: 'eggs, milk, etc.',
        done: false
      }
    ];

    return todos;
  }
}

créer une classe abstraite

export interface Todo {
  title: string;
  description: string;
  done: boolean;
}

@Injectable()
export abstract class TodoListService {
  abstract getTodos(): Todo[];
}

Utilisation dans le composant

providers: [
    { provide: TodoListService, useClass: TodoListPublicService }
  ]
export class TodoListComponent implements OnInit {
  todos: Todo[];

  constructor(private todoListService: TodoListService) { }

  ngOnInit() {
  }

2voto

Celso Bring Points 23

Utilisez OpaqueToken, les interfaces ne sont pas supportées par DI, car Javascript lui-même n'a pas d'interfaces. Une façon de le faire dans Angular 2 est d'utiliser OpaqueToken. https://angular.io/docs/ts/latest/guide/dependency-injection.html

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

export let APP_CONFIG = new OpaqueToken('app.config');

providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]

constructor(@Inject(APP_CONFIG) config: AppConfig) {
  this.title = config.title;
}

J'espère que cela pourra vous aider.

2 votes

OpaqueToken est déprécié dans Angular v5, avec InjectionToken en tant que remplaçant

1voto

Dacian Points 261

Mon expérience (venant du développement backend java) dans le développement frontend est la suivante.

Si nous parlons d'"interface", j'ai la ferme conviction qu'un principe fondamental de l'utilisation de l'interface est garanti par les langages qui "offrent" une interface. Ce principe est le suivant : "codez contre l'interface et non contre l'implémentation".

Cela semble ne pas être assuré par typescript/angular2. (qu'ils ne devraient pas encore utiliser l'interface word, peut-être).

Quel était mon cas (avertissement : Je suis en train d'apprendre angular2, donc ma solution de contournement pourrait sembler laide aux utilisateurs avancés) :
Le composant A1 a un composant enfant B.
Le composant B doit connaître le parent et appeler une méthode sur le parent.
Ainsi, le composant B reçoit le parent via l'injection de dépendance dans son constructeur.

   constructor( private a: A1Component) {}

Tout va bien.
Ensuite, les choses se compliquent.
Un autre composant A2 peut être le parent de comp. B.
Idéalement, je devrais injecter dans B une interface (pas une implémentation) qui est implémentée à la fois par A1 et A2 (ce qui serait naturel dans le monde Java).
Alors que B fonctionnerait avec cette interface. Si nécessaire, un typecast vers A2, par exemple, permettrait à B de savoir si l'instance qu'il possède est réellement A2 ou non.

Je parle de simples composants/classes, pas de services (je vois que la plupart des solutions font référence à des services).
J'ai essayé d'utiliser @Host(), @Injectable(), OpaqueToken, Providers mais il y avait toujours une erreur. Mais à la fin, cela semble fonctionner : en réalité, l'objet injecté dans le composant B était un objet vide, et non le parent - peut-être ai-je mal utilisé les providers et un nouvel objet vide a été créé au lieu d'injecter l'objet parent.

Ce que j'ai fait à la fin : Je n'ai pas utilisé d'interface.
J'ai créé une classe de base simple pour A1 et A2 - appelons-la ABase.
Le composant B conserverait une référence à cette classe de base. La référence sera définie dans le constructeur comme ceci :

//BComponent:
 parent: ABase;    

 constructor(@Optional parentA1: A1Component, @Optional parentA2: A2Component) {
    if( parentA1 )
       this.parent = parentA1;
    else
       this.parent = parentA2
 }

Oui, c'est une solution de contournement étrange, pas très agréable (venant du monde Java, je suis d'accord) - mais je n'ai pas eu le temps et j'ai été déçu par l'aspect "interface".

Mise à jour de

Je reconsidère la réponse précédente (c'est mauvais design, laid ... était à mes débuts avec angular)

La documentation d'Angular contient désormais une description claire de ce problème précis : trouver le parent d'un composant.

Impossible d'utiliser l'interface - car l'interface ne peut pas être injectée.

"Rechercher des composants qui implémentent une interface serait mieux. Ce n'est pas possible car les interfaces TypeScript disparaissent du JavaScript transposé, qui ne prend pas en charge les interfaces. Il n'y a pas d'artefact à rechercher."

Ne peut pas utiliser classe de base des parents possibles ni l'un ni l'autre ... (c'est la cause profonde de ma précédente réponse désespérée et mauvaise).

Qu'est-ce qui fonctionne ? La technique : trouver un parent par son interface de classe .

Principalement :

L'enfant B voit un parent général Parent (peut être A1Component ou A2Component)

export class BComponent {
  name = 'B';
  constructor( @Optional() public parent: Parent ) { }
}

Et chaque composant parent possible fournit un Parent (au niveau des composants ! !!) en utilisant classe-interface :

providers: [{ provide: Parent, useExisting: forwardRef(() => A1Component) }],

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X