web-dev-qa-db-fra.com

Programmation basée sur interface avec TypeScript, Angular 2 & SystemJS

J'essaie actuellement de nettoyer du code afin de programmer des interfaces plutôt que des implémentations, mais je ne sais pas comment.

Pour être plus précis, j'utilise: * TypeScript 1.5.0 beta -> transpilé en ES5/commonjs * * SystemJS pour charger les modules

J'essaie actuellement d'utiliser des modules externes comme suit:

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

et ce module est importé dans 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;
        ...
    }

    ...
}

Le code ci-dessus est compilé correctement, mais mon problème se résume essentiellement au fait que Angular n'injecte pas d'instance de PostsServiceImpl dans le composant Posts.

Bien sûr, c'est simplement parce que je ne trouve pas le bon moyen de tout déclarer/exporter/importer

Sans export interface PostsService ..., j'obtiens des erreurs de compilation TS parce que je l'utilise dans le contrôleur de publication.

Sans export var PostsService:PostsServiceImpl; j'obtiens des erreurs de compilation TS dans le viewInjector du décorateur @View

Dans le code js généré, je ne trouve que exports.PostsService; qui est ajouté à cause de l'exportation var ... Je sais que les interfaces disparaissent au moment de l'exécution.

Donc je suis un peu perdu dans tout ça. Des idées pratiques sur l'utilisation de la programmation par interface avec TypeScript & Angular 2?

8
dSebastien

Toutes les idées pratiques sur l'utilisation de la programmation par interface avec TypeScript & Angular 2

Les interfaces sont effacées lors de l'exécution. En fait, les décorateurs ne supportent pas les interfaces non plus. Il est donc préférable d’utiliser quelque chose qui existe au moment de l’exécution (par exemple, les implémentations).

6
basarat

N'oubliez pas que votre classe peut (et devrait) dépendre d'abstractions , mais il existe un endroit où vous ne pouvez pas utiliser l'abstraction: c'est dans l'injection de dépendance d'Angular. 

Angular doit savoir quelle implémentation utiliser.

Exemple :

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

Donc, dans votre test, vous pouvez facilement utiliser des simulacres car tous vos contrôleurs/services, etc., dépendent d’abstractions. Mais pour l'application réelle, Angular a besoin de mises en œuvre.

4

TypeScript ne produit aucun symbole pour les interfaces, mais vous aurez besoin d'un symbole quelconque pour que l'injection de dépendance fonctionne.

Solution: utilisez OpaqueTokens comme symbole, assignez une classe à ce jeton via la fonction Provide () et utilisez la fonction Inject avec ce jeton dans le constructeur de votre classe.

Dans l'exemple ci-dessous, j'ai supposé que vous vouliez affecter PostsServiceImpl à PostsService dans le composant Posts, car il est plus facile d'expliquer lorsque le code se trouve au même endroit. Les résultats de l'appel de provision () peuvent également être placés dans le deuxième argument de angular.bootstrap (someComponent, arrayOfProviders) ou dans le tableau supply [] d'un composant situé plus haut dans la hiérarchie que le composant Posts.

Dans components/posts/posts.service.ts:

import {OpaqueToken} from "@angular/core";
export let PostsServiceToken = new OpaqueToken("PostsService");

Dans votre constructeur:

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

    ...
}    
3
PotatoEngineer

Dans TypeScript, vous pouvez implémenter des classes.

Exemple

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

Vous pouvez ensuite fournir le service avec différentes implémentations.

1
Matthias