2015-07-03 6 views
8

Ich versuche zur Zeit einige Code, um aufzuräumen gegen Schnittstellen zu programmieren, anstatt gegen Implementierungen kann aber nicht herausfinden, wie.Schnittstelle basierte Programmierung mit Typoskript, Angular 2 & SystemJS

Um genauer zu sein, ich verwende: * Typoskript 1.5.0 Beta -> transpiled bis ES5/Commonjs * SystemJS die Module

zu laden ich versuchen derzeit externe Module wie folgt zu verwenden:

posts.service.ts Datei:

///<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>{ 
    ... 
} 

und das Modul in posts.ts importiert wird:

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

    ... 
} 

Der obige Code kompiliert korrekt, aber im Grunde kocht mein Problem auf die Tatsache, dass Angular nicht eine Instanz von PostsServiceImpl in den Beiträgen Komponente injizieren.

Natürlich ist es einfach, weil ich zu erklären/Export nicht die richtige Art und Weise finden/importieren alles

Ohne export interface PostsService ..., ich TS Kompilierungsfehlern bekommen, weil ich es in den Pfosten-Controller verwenden.

Ohne export var PostsService:PostsServiceImpl; bekomme ich TS Kompilierungsfehlern in dem viewInjector des @View Dekorateur

Im generierten Code js ich nur exports.PostsService; finden, die wegen des Exports var hinzugefügt wird ... Ich weiß, dass Schnittstellen zur Laufzeit verschwinden.

Also bin ich ein bisschen verloren in all dem. Irgendwelche praktischen Ideen, wie ich Schnittstelle basierte Programmierung w/TypeScript & Angular 2 verwenden kann?

Antwort

6

Jede praktische Ideen, wie ich Interface-basierte Programmierung w/Typoskript & Angular 2

Interfaces gelöscht zur Laufzeit verwenden können. Decorators unterstützen auch keine Schnittstellen. Sie sollten also besser etwas verwenden, das zur Laufzeit existiert (d. H. Implementierungen).

+2

Das sind traurige Nachrichten für mich ;-). Ich denke, ich sollte mich einfach anpassen und meine komplett typisierte Java-Welt vergessen. ^^ – dSebastien

+3

Wie soll ich dann in meiner Anwendung die Entkopplung angehen? Ich bin noch nicht da, aber mein Ziel ist es, testbaren Code zu schreiben. Afaik, es gibt noch keine Unterstützung für abstrakte Klassen in TypeScript, oder? – dSebastien

+1

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

4

Sie haben im Auge behalten, dass Ihre Klasse kann (und soll) hängt von Abstraktionen, aber es gibt einen Ort, wo man nicht Abstraktion verwenden kann: es ist in der Abhängigkeit Injektion von Angular.

Angular muss wissen, welche Implementierung zu verwenden ist.

Beispiel:

/// <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]); 
} 

Also in Ihrem Test, den Sie spottet einfach wie alle Controller/Dienstleistungen usw. hängen von Abstraktionen nutzen können. Aber für die reale Anwendung benötigt eckige Implementierungen.

+0

Nun meine (naive) Idee war, 'bind (PostsService) .toClass (PostServiceImpl)' genau dafür zu verwenden, Angular zu wissen, wie man abbildet abstrakt zu konkret. Das hätte Sinn machen können, wenn die Schnittstellen zur Laufzeit nicht gelöscht würden, wie es @basarat gezeigt hat. Was den Client-Code betrifft, würde ich gerne die Tatsache verbergen, dass eine bestimmte Implementierung injiziert wurde. – dSebastien

3

TypeScript erzeugt keine Symbole für Schnittstellen, aber Sie benötigen ein Symbol, um die Abhängigkeitsinjektion zu ermöglichen.

Lösung: Verwenden Sie OpaqueTokens als Ihr Symbol, weisen Sie diesem Token über die Funktion provide() eine Klasse zu und verwenden Sie die Inject-Funktion mit diesem Token im Konstruktor Ihrer Klasse.

Im Beispiel hier habe ich angenommen, dass Sie PostsServiceImpl PostsService in der Posts-Komponente zuweisen möchten, weil es einfacher ist zu erklären, wenn der Code an der gleichen Stelle ist. Die Ergebnisse des Aufrufs von provide() könnten auch in das zweite Argument von angular.bootstrap (someComponent, arrayOfProviders) oder in das Array [provide] einer Komponente eingefügt werden, die höher als die Posts-Komponente in der Hierarchie liegt.

In Komponenten/posts/posts.service.ts:

In Ihrem Konstruktor:

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

In Typoskript Sie Klassen implementieren können.

Beispiel

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

Sie können dann den Dienst mit unterschiedlichen Implementierungen bieten.

+0

Ich habe diesen Ansatz verwendet und es ist sehr effektiv. – wickdninja

+0

Ich neige dazu, die Basisklasse immer noch mit I voranzuzählen (d. H. IConfigService), obwohl es keine tatsächliche Schnittstelle ist. Diese Namenskonvention kommuniziert zumindest meine Absichten für die Klasse. – wickdninja