2015-07-03 34 views
8

Attualmente sto cercando di ripulire del codice per programmare contro interfacce anziché contro implementazioni ma non riesco a capire come.Programmazione basata sull'interfaccia con TypeScript, Angular 2 e SystemJS

Per essere più precisi, sto usando: * dattiloscritto 1.5.0 beta -> transpiled a ES5/commonjs * SystemJS di caricare i moduli

Attualmente cerco di utilizzare moduli esterni come segue:

il file

posts.service.ts:

///<reference path="../../../typings/tsd.d.ts" /> 
///<reference path="../../../typings/typescriptApp.d.ts" /> 
... 
export interface PostsService{ // 1 
    fetchPosts(): Rx.Observable<any>; 
} 
export var PostsService:PostsServiceImpl; // 2 
... 
export class PostsServiceImpl implements PostsService { // 3 
    ... 
    constructor(){ 
     console.log('Loading the Posts service'); 
} 
... 
fetchPosts(): Rx.Observable<any>{ 
    ... 
} 

e quel modulo viene importato in posts.ts:

///<reference path="../../../typings/tsd.d.ts" /> 
    ///<reference path="../../../typings/typescriptApp.d.ts" /> 

    import {PostsService, PostsServiceImpl} from 'components/posts/posts.service'; 

    @Component({ 
    selector: 'posts', 
    viewInjector: [ 
     //PostsServiceImpl 
     //PostsService 
     bind(PostsService).toClass(PostsServiceImpl) 
    ] 
}) 
... 
export class Posts { 
    private postsServices: PostsService; 

    constructor(postsService: PostsService) { 
     console.log('Loading the Posts component'); 
     this.postsServices = postsService; 
     ... 
    } 

    ... 
} 

Il codice sopra riportato viene compilato correttamente, ma fondamentalmente il mio problema si riduce al fatto che Angular non inietterà un'istanza di PostsServiceImpl nel componente Post.

Certo che è semplicemente perché non trovo il modo corretto di dichiarare/export/importare tutto

Senza export interface PostsService ..., ricevo errori di compilazione TS perché lo uso nel controller messaggi.

Senza export var PostsService:PostsServiceImpl; ottengo gli errori di compilazione TS nel decoratore @View viewInjector

Nel codice generato js Ho solo trovato exports.PostsService; che si aggiunge a causa del var esportazione ... So che le interfacce scompaiono in fase di esecuzione.

Quindi sono un po 'perso in tutto questo. Qualche idea pratica su come utilizzare la programmazione basata sull'interfaccia w/TypeScript & Angular 2?

risposta

6

Tutte le idee pratiche come posso usare programmazione basato interfaccia w/tipografico & angolare 2

interfacce vengono cancellati in fase di esecuzione. In effetti, i decoratori non supportano neanche le interfacce. Quindi è meglio usare qualcosa che esiste al runtime (cioè implementazioni).

+2

Questa è una triste notizia per me ;-). Credo che dovrei solo adattare e dimenticare il mio mondo Java completamente digitato .. ^^ – dSebastien

+3

Come dovrei quindi affrontare il disaccoppiamento nella mia applicazione? Non ci sono ancora, ma il mio obiettivo è scrivere un codice verificabile. Afaik non c'è ancora il supporto per le classi astratte in TypeScript, giusto? – dSebastien

+1

Presto: https://github.com/Microsoft/TypeScript/pull/3579 – basarat

4

devi tenere a mente che vostra classe può (e dovrebbe) dipendere astrazioni, ma c'è un posto in cui non è possibile utilizzare l'astrazione: è nel iniezione di dipendenza di angolare.

Angolare deve sapere quale implementazione utilizzare.

Esempio:

/// <reference path="../../../../../_reference.ts" /> 

module MyModule.Services { 
    "use strict"; 

    export class LocalStorageService implements ILocalStorageService { 
     public set = (key: string, value: any) => { 
      this.$window.localStorage[key] = value; 
     }; 

     public get = (key: string, defaultValue: any) => { 
      return this.$window.localStorage[key] || defaultValue; 
     }; 

     public setObject = (key: string, value: any) => { 
      this.$window.localStorage[key] = JSON.stringify(value); 
     }; 

     public getObject = (key: string) => { 
      if (this.$window.localStorage[key]) { 
       return JSON.parse(this.$window.localStorage[key]); 
      } else { 
       return undefined; 
      } 
     }; 

     constructor(
      private $window: ng.IWindowService // here you depend to an abstraction ... 
      ) { } 
    } 
    app.service("localStorageService", 
     ["$window", // ... but here you have to specify the implementation 
      Services.LocalStorageService]); 
} 

Quindi nel tuo test che si può usare schernisce facilità con cui tutti i controller/servizi ecc dipendono astrazioni. Ma per la vera applicazione, l'angolare ha bisogno di implementazioni.

+0

Beh, la mia idea (ingenua) era di usare 'bind (PostsService) .toClass (PostsServiceImpl)' esattamente per questo, lasciando che Angular sapesse come mappare da astratto al concreto. Questo avrebbe potuto avere senso se le interfacce non fossero state cancellate in runtime come sottolineato da @basarat. Per quanto riguarda il codice client, vorrei nascondere completamente il fatto che una specifica implementazione veniva iniettata. – dSebastien

3

TypeScript non produce alcun simbolo per le interfacce, ma è necessario un simbolo di qualche tipo per eseguire il lavoro di inserimento delle dipendenze.

Soluzione: utilizzare OpaqueTokens come simbolo, assegnare una classe a tale token tramite la funzione provide() e utilizzare la funzione Inject con quel token nel costruttore della classe.

Nell'esempio qui, ho assunto che si voglia assegnare PostsServiceImpl a PostsService nel componente Post, perché è più facile spiegare quando il codice si trova tutti nello stesso posto. I risultati della chiamata a provide() possono anche essere inseriti nel secondo argomento di angular.bootstrap (someComponent, arrayOfProviders) o nella matrice [] di un componente più in alto nella gerarchia rispetto al componente Posts.

In componenti/messaggi/posts.service.ts:

nel costruttore:

import {PostsService, PostsServiceImpl, PostsServiceToken} from 'components/posts/posts.service'; 
import {provide} from "@angular/core"; 
@Component({ 
    selector: 'posts', 
    providers: [provide(PostsServiceToken, {useClass: PostsServiceImpl})] 
}) 
export class Posts { 
    private postsServices: PostsService; 

    constructor(
     @Inject(PostsServiceToken) 
     postsService: PostsService 
     ) { 
     console.log('Loading the Posts component'); 
     this.postsServices = postsService; 
     ... 
    } 

    ... 
}  
1

a macchina è possibile implementare le classi.

Esempio

config.service.ts:

export class ConfigService { 
    HUB_URL: string; 
} 

export class ConfigServiceImpl implements ConfigService { 
    public HUB_URL = 'hub.domain.com'; 
} 

export class ConfigServiceImpl2 implements ConfigService { 
    public HUB_URL = 'hub2.domain.com'; 
} 

app.component.ts:

import { Component } from '@angular/core'; 
import { ConfigService, ConfigServiceImpl, ConfigServiceImpl2 } from './config.service'; 

@Component({ 
    ... 
    providers: [ 
    { provide: ConfigService, useClass: ConfigServiceImpl } 
    //{ provide: ConfigService, useClass: ConfigServiceImpl2 } 
    ] 
}) 
export class AppComponent { 
} 

È quindi possibile fornire il servizio con diverse implementazioni.

+0

Ho usato questo approccio ed è abbastanza efficace. – wickdninja

+0

Tendenzialmente ho ancora il prefisso della classe base con I (cioè IConfigService) anche se non è un'interfaccia reale. Questa convenzione di denominazione comunica almeno le mie intenzioni per la classe. – wickdninja

Problemi correlati