135 votes

Héritage et injection de dépendances

J'ai un ensemble de composants angular2 qui devraient tous recevoir un service injecté. Ma première idée était qu'il serait préférable de créer une superclasse et d'y injecter le service. Tous mes composants devraient alors étendre cette superclasse, mais cette approche ne fonctionne pas.

Exemple simplifié :

export class AbstractComponent {
  constructor(private myservice: MyService) {
    // Inject the service I need for all components
  }
}

export MyComponent extends AbstractComponent {
  constructor(private anotherService: AnotherService) {
    super(); // This gives an error as super constructor needs an argument
  }
}

Je pourrais résoudre ce problème en injectant MyService à l'intérieur de chaque composant et utiliser cet argument pour les super() mais c'est définitivement une sorte d'absurdité.

Comment organiser correctement mes composants pour qu'ils héritent d'un service de la super classe ?

0 votes

Il ne s'agit pas d'un doublon. La question à laquelle il est fait référence concerne la construction d'une classe DERIVÉE qui peut accéder à un service injecté par une super classe déjà définie. Ma question est de savoir comment construire une classe SUPER qui hérite d'un service pour les classes dérivées. C'est simplement l'inverse.

0 votes

Votre réponse (en ligne dans votre question) n'a pas de sens pour moi. De cette façon, vous créez un injecteur qui est indépendant de l'injecteur qu'Angular utilise pour votre application. Utilisation de new MyService() au lieu de l'injection vous donne exactement le même résultat (sauf qu'il est plus efficace). Si vous voulez partager la même instance de service entre différents services et/ou composants, cela ne fonctionnera pas. Chaque classe recevra une autre MyService instance.

0 votes

Vous avez tout à fait raison, mon code va générer beaucoup d'instances de myService . J'ai trouvé une solution qui évite cela mais qui ajoute plus de code aux classes dérivées...

94voto

Günter Zöchbauer Points 21340

Je pourrais résoudre ce problème en injectant MyService dans chaque composant et en utilisant cet argument pour l'appel à super() mais c'est définitivement absurde.

Ce n'est pas absurde. C'est ainsi que fonctionnent les constructeurs et l'injection de constructeurs.

Chaque classe injectable doit déclarer les dépendances en tant que paramètres du constructeur et si la superclasse a également des dépendances, celles-ci doivent être listées dans le constructeur de la sous-classe et transmises à la superclasse avec l'attribut super(dep1, dep2) appeler.

Passer autour d'un injecteur et acquérir des dépendances de manière impérative présente de sérieux inconvénients.

Il masque les dépendances, ce qui rend le code plus difficile à lire.
Il viole les attentes de ceux qui connaissent le fonctionnement d'Angular2 DI.
Il brise la compilation hors ligne qui génère du code statique pour remplacer les DI déclaratives et impératives afin d'améliorer les performances et de réduire la taille du code.

0 votes

Si je dois passer le service nécessaire de chaque classe dérivée à la classe supérieure, il est inutile d'essayer de l'injecter dans la classe supérieure. Il suffit de l'injecter dans chaque classe dérivée. Moins de code, une meilleure lisibilité du code.

0 votes

Bien sûr, il suffit de l'injecter partout où vous en avez besoin. Si votre superclasse a une implémentation qui en dépend, ajoutez-la au constructeur pour que les sous-classes soient obligées de la passer, sinon ne le faites pas.

8 votes

Pour que ce soit clair : j'en ai besoin PARTOUT. J'essaie de déplacer cette dépendance vers ma super classe afin que CHAQUE classe dérivée puisse accéder au service sans avoir à l'injecter individuellement dans chaque classe dérivée.

81voto

maxhb Points 5802

Solution mise à jour, empêche la génération de plusieurs instances de myService en utilisant l'injecteur global.

import {Injector} from '@angular/core';
import {MyServiceA} from './myServiceA';
import {MyServiceB} from './myServiceB';
import {MyServiceC} from './myServiceC';

export class AbstractComponent {
  protected myServiceA:MyServiceA;
  protected myServiceB:MyServiceB;
  protected myServiceC:MyServiceC;

  constructor(injector: Injector) {
    this.settingsServiceA = injector.get(MyServiceA);
    this.settingsServiceB = injector.get(MyServiceB);
    this.settingsServiceB = injector.get(MyServiceC);
  }
}

export MyComponent extends AbstractComponent {
  constructor(
    private anotherService: AnotherService,
    injector: Injector
  ) {
    super(injector);

    this.myServiceA.JustCallSomeMethod();
    this.myServiceB.JustCallAnotherMethod();
    this.myServiceC.JustOneMoreMethod();
  }
}

Cela garantira que MyService peut être utilisé dans toute classe qui étend AbstractComponent sans qu'il soit nécessaire d'injecter MyService dans chaque classe dérivée.

Cette solution présente quelques inconvénients (voir le commentaire de @Günter Zöchbauer sous ma question initiale) :

  • L'injection de l'injecteur global n'est une amélioration que lorsqu'il y a plusieurs services différents qui doivent être injectés à plusieurs endroits. Si vous n'avez qu'un seul service partagé, il est probablement mieux/plus facile d'injecter ce service dans la ou les classes dérivées.
  • Ma solution et l'alternative qu'il propose ont toutes deux l'inconvénient de rendre plus difficile de voir quelle classe dépend de quel service.

Pour une explication très bien écrite de l'injection de dépendances dans Angular2, voir cet article de blog qui m'a beaucoup aidé à résoudre le problème : http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html

7 votes

Il est donc assez difficile de comprendre quels services sont réellement injectés.

1 votes

Cela ne devrait-il pas être this.myServiceA = injector.get(MyServiceA); etc.

11 votes

La réponse de @Gunter Zochbauer est la bonne. Ce n'est pas la façon correcte de le faire et casse beaucoup de conventions angulaires. Il pourrait être plus simple dans la mesure où le codage de tous ces appels d'injection est une "douleur", mais si vous voulez sacrifier le fait d'avoir à écrire du code de constructeur pour être en mesure de maintenir une grande base de code, alors vous vous tirez dans le pied. Cette solution n'est pas extensible, IMO, et causera beaucoup de bugs déroutants sur la route.

7voto

Leukipp Points 392

Au lieu d'injecter tous les services manuellement, j'ai créé une classe fournissant les services, par exemple, elle obtient l'injection des services. Cette classe est ensuite injectée dans les classes dérivées et transmise à la classe de base.

Classe dérivée :

@Component({
    ...
    providers: [ProviderService]
})
export class DerivedComponent extends BaseComponent {
    constructor(protected providerService: ProviderService) {
        super(providerService);
    }
}

Classe de base :

export class BaseComponent {
    constructor(protected providerService: ProviderService) {
        // do something with providerService
    }
}

Classe de prestataires de services :

@Injectable()
export class ProviderService {
    constructor(private _apiService: ApiService, private _authService: AuthService) {
    }
}

3 votes

Le problème est que vous risquez de créer un service "tiroir à ordures" qui n'est essentiellement qu'un proxy pour le service Injector.

2voto

maximedupre Points 204

Au lieu d'injecter un service qui a tous les autres services comme dépendants, comme ceci :

class ProviderService {
    constructor(private service1: Service1, private service2: Service2) {}
}

class BaseComponent {
    constructor(protected providerService: ProviderService) {}

    ngOnInit() {
        // Access to all application services with providerService
        this.providerService.service1
    }
}

class DerivedComponent extends BaseComponent {
    ngOnInit() {
        // Access to all application services with providerService
        this.providerService.service1
    }
}

Je sauterais cette étape supplémentaire et ajouterais simplement l'injection de tous les services dans le BaseComponent, comme ceci :

class BaseComponent {
    constructor(protected service1: Service1, protected service2: Service2) {}
}

class DerivedComponent extends BaseComponent {
    ngOnInit() {
        this.service1;
        this.service2;
    }
}

Cette technique suppose 2 choses :

  1. Votre préoccupation est entièrement liée à l'héritage des composants. Très probablement, la raison pour laquelle vous avez atterri sur cette question est la quantité écrasante de code non sec (WET ?) que vous devez répéter dans chaque classe dérivée. Si vous voulez bénéficier d'un point d'entrée unique pour tous vos composants et services vous devrez faire l'étape supplémentaire.

  2. Chaque composant étend le BaseComponent

Il y a également un inconvénient si vous décidez d'utiliser le constructeur d'une classe dérivée, car vous devrez appeler super() et passe toutes les dépendances. Bien que je ne vois pas vraiment de cas d'utilisation qui nécessite l'utilisation de l'option constructor au lieu de ngOnInit il est tout à fait possible qu'un tel cas d'utilisation existe.

2 votes

La classe de base a ensuite des dépendances sur tous les services dont l'un de ses enfants a besoin. ChildComponentA a besoin de ServiceA ? Eh bien maintenant, ChildComponentB a aussi besoin de ServiceA.

-1voto

dlnsk Points 21

Si la classe parent a été obtenue à partir d'un plug-in tiers (et que vous ne pouvez pas en changer la source), vous pouvez le faire :

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

export MyComponent extends AbstractComponent {
  constructor(
    protected injector: Injector,
    private anotherService: AnotherService
  ) {
    super(injector.get(MyService));
  }
}

ou la meilleure façon (ne garder qu'un seul paramètre dans le constructeur) :

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

export MyComponent extends AbstractComponent {
  private anotherService: AnotherService;

  constructor(
    protected injector: Injector
  ) {
    super(injector.get(MyService));
    this.anotherService = injector.get(AnotherService);
  }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X