web-dev-qa-db-fra.com

Angular 2: Injecter le service dans la classe

J'ai angular qui représente la forme. Je veux pouvoir instancier plusieurs instances de cette classe en utilisant le constructeur.

Le constructeur prend plusieurs arguments représentant les propriétés de cette forme.

constructor(public center: Point, public radius: number, fillColor: string,
    fillOpacity: number, strokeColor: string, strokeOpacity: number, zIndex: number)

Dans ma classe, je veux utiliser un service qui permet de dessiner des formes sur la carte. Est-il possible d'injecter ce service dans ma classe et d'utiliser toujours le constructeur de la manière standard.

Je veux donc faire quelque chose comme ci-dessous et avoir Angular résoudre automatiquement la dépendance injectée.

constructor(public center: GeoPoint, public radius: number, 
    fillColor: string, fillOpacity: number, strokeColor: string, strokeOpacity: number, 
    zIndex: number, @Inject(DrawingService) drawingService: DrawingService)
26
Liplattaa

J'ai réussi à résoudre mon problème.

Angular 2 - 4 fournit un injecteur réfléchissant qui permet d'injecter des dépendances en dehors des paramètres du constructeur.

Tout ce que j'avais à faire était d'importer l'injecteur réfléchissant de @angular/core.

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

Et alors:

let injector = ReflectiveInjector.resolveAndCreate([DrawingService]);
this.drawingApi = injector.get(DrawingService);

La classe n'a même pas besoin d'être décorée avec le @Injectable décorateur. Le seul problème est que je dois fournir toutes les dépendances pour DrawingService et toutes les dépendances imbriquées, ce qui est difficile à maintenir.

MODIFIER :

Angulaire 5

import { Injector } from "@angular/core";

const injector = Injector.create([
    { provide: DrawingService }
]);
this.drawingApi = injector.get(DrawingService);

Angulaire 6

import { Injector } from "@angular/core";

const injector = Injector.create({ 
  providers: [ 
    { provide: DrawingService },
  ]
});
this.drawingApi = injector.get(DrawingService);
37
Liplattaa

Voici deux autres façons possibles d'obtenir le résultat souhaité ou très similaire.

Première approche - utiliser un gestionnaire pour vos entités ou objets non-service

Vous disposez d'un ou plusieurs service (s) d'usine chargé (s) d'instancier vos objets.

Cela signifie que it peut fournir les paramètres requis plus loin aux objets, et ne nécessite pas que vous les transmettiez vous-même.

Par exemple, supposons que vous ayez des entités en tant que hiérarchie de classes:

abstract class Entity { }

class SomeEntity extends Entity { 
   ...
}

Vous pouvez alors avoir un EntityManager qui est un service et peut construire des entités:

@Injectable()   // is a normal service, so DI is standard
class EntityManager {

  constructor(public http: Http) { }    // you can inject any services now

  create<E extends Entity>(entityType: { new(): E; }): E {
    const entity = new entityType();    // create a new object of that type
    entity.manager = this;              // set itself on the object so that that object can access the injected services like http - one can also just pass the services not the manager itself
    return entity;
  }

}

Vous pouvez également avoir des paramètres de construction si vous le souhaitez (mais ils n'auront aucune information de type car create doit fonctionner avec tous les types d'entités):

class SomeEntity extends Entity { 
   constructor(param1, param1) { ... }
}

// in EntityManager
create<E extends Entity>(entityType: { new(): E; }, ...params): E {
    const entity = new entityType(...params);
    ...
}

Vos entités peuvent désormais déclarer le responsable:

abstract class Entity {
  manager: EntityManager;
}

Et vos entités peuvent l'utiliser pour faire n'importe quoi:

class SomeEntity extends Entity {
  doSomething() {
    this.manager.http.request('...');
  }
}

Maintenant, chaque fois que vous devez créer une entité/un objet, vous utilisez ce gestionnaire. EntityManager doit être injecté lui-même mais les entités sont des objets libres. Mais tout le code angular démarrera à partir d'un contrôleur ou d'un service ou autre donc il sera possible d'injecter le gestionnaire.

// service, controller, pipe, or any other angular-world code

constructor(private entityManager: EntityManager) {
    this.entity = entityManager.create(SomeEntity);
}

Cette approche peut également être adaptée à des objets arbitraires. Vous n'avez pas besoin d'une hiérarchie de classes, mais avec TypeScript, cela fonctionne mieux. Il est également logique d'avoir une classe de base pour vos objets, car vous pouvez également réutiliser le code à l'ancienne, en particulier dans une approche orientée domaine/objet.

PROS : Cette approche est plus sûre car elle réside toujours dans la hiérarchie DI complète et il devrait y avoir moins d'effets secondaires indésirables.

CONTRE : L'inconvénient est que vous ne pouvez plus jamais utiliser new, ni obtenir l'accès à ces services en code arbitraire. Vous devez toujours vous fier à la DI et à vos usines/usines.

Deuxième approche - h4ckz0rs

Vous créez un service dédié à l'obtention (via DI) des services dont vous avez besoin en objets.

Cette partie est très similaire à la première approche, juste que ce service n'est pas une usine. Au lieu de cela, il transmet les services injectés dans un objet qui est défini en dehors de cette classe dans un fichier différent (explication après). Par exemple:

...
import { externalServices } from './external-services';

@Injectable()
export class ExternalServicesService {

  constructor(http: Http, router: Router, someService: SomeService, ...) {
    externalServices.http = http;
    externalServices.router = router;
    externalServices.someService = someService;
  }

}

L'objet qui contiendra les services est défini dans son propre fichier comme tel:

export const externalServices: {
  http,
  router,
  someService
} = { } as any;

Notez que les services n'utilisent aucune information de type (c'est un inconvénient mais nécessaire).

Ensuite, vous devez vous assurer que ExternalServicesService est injecté une fois. Le meilleur endroit est d'utiliser le composant principal de l'application:

export class AppComponent {

  constructor(..., externalServicesService: ExternalServicesService) {

Enfin, vous pouvez maintenant utiliser les services dans n'importe quel objet arbitraire à tout moment après l'instanciation du composant principal de l'application.

import { externalServices } from '../common/externalServices' // or wherever is defined

export class SomeObject() {
    doSomething() {
        externalServices.http().request(...) // note this will be called after ng2 app is ready for sure
    }
}

Notez que vous ne pourrez appeler aucun de ces services dans le code de classe ou dans des objets non instanciés après l'application est instanciée. Mais dans une application typique, cela ne devrait jamais être nécessaire.

Maintenant, quelques explications sur cette configuration bizarre:

Pourquoi utiliser un objet externalServices dans un fichier séparé au lieu du même fichier ou simplement enregistrer les services sur la classe elle-même (en tant qu'attributs statiques) et pourquoi les services sont-ils non typés?

La raison en est que lorsque vous intégrez le code, par exemple via angular-cli/webpack avec --prod mode, il est très probable d'obtenir des dépendances cycliques qui ne peuvent pas être résolues correctement et vous obtiendrez des erreurs laides qui sont difficiles à trouver - j'ai déjà vécu cela :).

Une erreur de forme

Impossible de lire la propriété "prototype" de non défini

vu uniquement lors de l'exécution avec --prod flag indiquera que les dépendances ne sont pas résolues correctement.

Il est préférable de s'assurer que ExternalServicesService ne dépend que de externalServices pour passer les instances de service, et l'application injecte uniquement ExternalServicesService une fois (par exemple dans votre AppComponent principal) puis tout arbitraire le code/les objets n'utiliseront que externalServices pour obtenir les services.

Ainsi, tout code de ce type n'aura qu'à importer le externalServices qui n'a plus de deps (car les services ne sont pas non plus tapés). S'ils devaient importer ExternalServicesService, ils auraient importé tout le reste et n'auraient pas pu résoudre statiquement les dépôts bidirectionnels. Et cela devient un problème majeur dans ng2/webpack lors du regroupement pour prod.

La même chose se produirait si nous devions utiliser des types pour les services, car cela nécessitera imports.

PROS : Cette approche est plus facile à utiliser une fois la configuration effectuée, et vous êtes libre d'utiliser new. Fondamentalement, n'importe quel fichier de code peut importer le externalServices et avoir un accès instantané aux services que vous souhaitez exposer de cette façon.

CONS: L'inconvénient est la configuration hackish et les problèmes possibles causés par les deps cycliques. Il est également plus sensible, car vous ne pouvez pas être sûr que externalServices dispose de ces services immédiatement. Ils ne seront définis qu'une fois l'application ng2 démarrée et le ExternalServicesService injecté pour la première fois. Un inconvénient est également que vous n'avez plus d'informations de type sur ces services.


PS: je ne sais pas pourquoi ce sujet n'est pas plus populaire.

Par exemple, être un fan de la conception orientée domaine, avoir des entités puissantes (par exemple avec des méthodes dirigées vers REST ou interagir avec d'autres services) est important et cette limitation a toujours compliqué les choses.

Nous avons dû surmonter cette limitation à la fois dans angularjs et maintenant à nouveau dans Angular2 + car elle ne semble toujours pas être abordée dans la bibliothèque.

16
Ovidiu Dolha

À partir de Angular 5.x:

import { Injector } from "@angular/core";
export class Model {

    static api: Api;

    constructor(data: any) {

        // check the api ref not exist
        // We don't want to initiate a new object every time
        if (!Model.api){
            //try inject my api service which use the HttpClient
            const injector: any = Injector.create([{ provide: Api, useClass: Api, deps: [] }]);
            Model.api = injector.get(Api);
        }

        // .....

    }
}
5
Eymen Elkum

Il y a un message de vojtajina sur GitHub qui fournit une belle approche à ce problème. Cette réponse est juste un lien, mais il est vraiment préférable de lire ceci dans son contexte car il y a d'autres informations intéressantes:

https://github.com/angular/di.js/issues/22#issuecomment-3677334

0
ndvo