2016-05-03 81 views
20

Mi chiedo se esiste un modo corretto per iniettare interfacce in Angular2? (vedi sotto)È possibile iniettare un'interfaccia con angular2?

Penso che questo sia correlato con il decoratore mancante @Injectable() sull'interfaccia, ma sembra che questo non sia permesso.

Saluti.

Quando CoursesServiceInterface viene implementato come interfaccia, il compilatore dattiloscritto lamenta "CoursesServiceInterface non riesce a trovare il nome":

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

ma con CoursesServiceInterface come interfaccia:

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; }; 
} 

Quando il servizio è una classe, il compilatore TypeScript non si lamenta più:

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; }; 
} 

risposta

39

No, le interfacce non sono supportate per DI. Con le interfacce TypeScript non sono più disponibili in fase di runtime, solo staticamente e quindi non possono essere utilizzate come token DI.

In alternativa è possibile utilizzare le stringhe come chiavi o InjectionToken

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

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

e iniettarlo come

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

Vedi anche https://angular.io/api/core/InjectionToken

+0

useClass: era quello che mi mancava ... peccato che non è nel tutorial ufficiale – Aligned

+1

https://angular.io/docs/ts/ ultimo/ricettario/dependency-injection.html #! # usevalue –

2

Usa OpaqueToken, le interfacce non sono supportate da DI, beacause Javascript per sé non hanno interfacce. Un modo per farlo in Angular 2 è usando 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; 
} 

Spero che questo può aiutare.

+0

OpaqueToken è obsoleto in Angular v5, con '' 'InjectionToken''' come sostituto – Ryan

-2

La mia esperienza (derivante dallo sviluppo del backend Java) in dev frontend è la seguente.

Se parliamo di "interfaccia" ho la forte aspettativa che un principio fondamentale di utilizzo dell'interfaccia sia assicurato dalle lingue che offrono un'interfaccia. Che è: 'codice contro interfaccia non contro implementazione'.

Questo sembra non essere garantito da dattiloscritto/angolare2. (di quanto non dovrebbero ancora usare l'interfaccia di parole, forse).

Qual è stato il mio caso (attenzione: sto imparando angular2 così la mia soluzione potrebbe sembrare brutto per gli utenti avanzati):
Componente A1 ha un componente figlio B.
Componente B dovrebbe conoscere il genitore e chiamare un metodo su genitore.
Quindi il componente B riceve il genitore tramite DependencyInjection nel suo costruttore.

constructor(private a: A1Component) {} 

Tutto va bene.
Le cose si complicano.
Un altro componente A2 può essere il genitore di comp. B.
Idealmente dovrei iniettare in B un'interfaccia (non un'implementazione) che sia implementata da A1 e A2 (questo sarebbe naturalmente nel mondo java).
Than B funzionerebbe con questa interfaccia. Se necessario, un typecast in A2 per esempio renderebbe B consapevole se l'istanza che ha è davvero A2 o no.

Parlo di semplici componenti/classi, non di servizi (vedo che la maggior parte delle soluzioni si riferisce ai servizi).
Ho provato a utilizzare @Host(), @Injectable(), OpaqueToken, Provider ma c'è sempre stato un errore. Quando alla fine è sembrato funzionare: in realtà l'oggetto iniettato nel Componente B era un oggetto vuoto, non il genitore - forse ho usato in modo sbagliato i provider e un nuovo oggetto vuoto è stato creato invece di iniettare l'oggetto genitore.

Cosa ho fatto alla fine: Non ho usato l'interfaccia.
Ho creato una classe base semplice per A1 e A2 - chiamiamola ABase.
Il componente B manterrà un riferimento a questa classe base. Riferimento dovrebbe essere impostato nel costruttore come questo:

//BComponent: 
parent: ABase;  

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

Sì, è una soluzione strana, non è bello (proveniente da Java pensare mondo, sono d'accordo) - ma ho appena finito il tempo e sono rimasto deluso per il 'interfaccia 'cosa.

22

Il motivo per cui non è possibile utilizzare le interfacce è perché un'interfaccia è un artefatto di progettazione in stile TypeScript. JavaScript non ha interfacce. L'interfaccia TypeScript scompare dal JavaScript generato. Non sono disponibili informazioni sul tipo di interfaccia per Angular da trovare in fase di esecuzione.


Soluzione 1:

La soluzione più semplice è solo per definire una classe astratta che implementa l'interfaccia. Spesso, hai comunque bisogno di una classe astratta.

Interfaccia:

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

export interface ProcessEngine { 

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

    getRoles(): Role[]; 
} 

astratta Classe:

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

export abstract class ProcessEngineService implements ProcessEngine { 

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

    abstract getRoles(): Role[]; 

} 

Calcestruzzo Classe:

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[] {...} 

} 

Ora è possibile definire il vostro provider come al solito:

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

Soluzione 2:

La documentazione ufficiale di angolare suggerisce di utilizzare la InjectionToken, simile a OpaqueToken.Qui è l'esempio:

L'interfaccia e classe:

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

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

Definisci il tuo Token:

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

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

Registrare il provider delle dipendenze utilizzando l'oggetto InjectionToken, ad esempio, nel app.module.ts:

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

Di quanto si può iniettare l'oggetto di configurazione in qualsiasi costruttore che necessita che, con l'aiuto di un decoratore @Inject:

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

Per coloro che hanno scelto Angular 4 che hanno trovato questa risposta, la Soluzione 2 è sicuramente il modo andare. Con questa configurazione, qualsiasi tipo di classi di simulazione può essere iniettato per test di unità e simili semplicemente cambiando i 'provider' con qualcosa come' provider: [{fornire: APP_CONFIG, useClass: AppConfigMockClass}] ' – Weikardzaena

+1

Questo è fantastico. Nota che se definisci un token nello stesso file dell'interfaccia, potresti ricevere avvisi non necessari: https://github.com/angular/angular-cli/issues/2034 – EvanM

+0

@Weikardzaena Per i test, cambi i fornitori nel vero AppModule, o possono essere impostati nei test stessi (o usando Ambiente ad es. test, dev, prod)? – Ryan