Ereditarietà e inserimento delle dipendenze


97

Ho una serie di componenti angular2 che dovrebbero ricevere un servizio di iniezione. Il mio primo pensiero è stato che sarebbe stato meglio creare una super classe e iniettare lì il servizio. Qualunque dei miei componenti estenderebbe quindi quella superclasse, ma questo approccio non funziona.

Esempio semplificato:

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

Potrei risolverlo inserendo MyServicein ogni singolo componente e usando quell'argomento per la super()chiamata, ma è decisamente una sorta di assurdo.

Come organizzare correttamente i miei componenti in modo che ereditino un servizio dalla super classe?


Questo non è un duplicato. La domanda a cui si fa riferimento riguarda come costruire una classe DERIVED che possa accedere a un servizio iniettato da una super classe già definita. La mia domanda riguarda come costruire una classe SUPER che eredita un servizio dalle classi derivate. È semplicemente il contrario.
maxhb

La tua risposta (in linea nella tua domanda) non ha senso per me. In questo modo crei un iniettore indipendente dall'iniettore che Angular utilizza per la tua applicazione. Usare new MyService()invece di iniettare ti dà esattamente lo stesso risultato (tranne che più efficiente). Se desideri condividere la stessa istanza di servizio tra diversi servizi e / o componenti, ciò non funzionerà. Ogni classe riceverà un'altra MyServiceistanza.
Günter Zöchbauer

Hai perfettamente ragione, il mio codice genererà molte istanze di myService. Trovato una soluzione che evita questo ma aggiunge più codice alle classi derivate ...
maxhb

L'iniezione dell'iniettore è solo un miglioramento quando ci sono diversi servizi diversi che devono essere iniettati in molti punti. Puoi anche iniettare un servizio che ha dipendenze da altri servizi e fornirli usando un getter (o metodo). In questo modo devi solo iniettare un servizio ma puoi usare un insieme di servizi. La tua soluzione e la mia alternativa proposta hanno entrambi lo svantaggio di rendere più difficile vedere quale classe dipende da quale servizio. Preferisco creare strumenti (come modelli live in WebStorm) che semplificano la creazione del codice boilerplate ed essere esplicito sulle dipendenze
Günter Zöchbauer

Risposte:


72

Potrei risolvere questo problema iniettando MyService all'interno di ogni singolo componente e utilizzare quell'argomento per la chiamata super (), ma è decisamente una sorta di assurdo.

Non è assurdo. Ecco come funzionano i costruttori e l'iniezione nel costruttore.

Ogni classe iniettabile deve dichiarare le dipendenze come parametri del costruttore e se anche la superclasse ha dipendenze, queste devono essere elencate anche nel costruttore della sottoclasse e passate alla superclasse con la super(dep1, dep2)chiamata.

Il passaggio di un iniettore e l'acquisizione di dipendenze presenta imperativamente gravi svantaggi.

Nasconde le dipendenze che rendono il codice più difficile da leggere.
Viola le aspettative di chi ha familiarità con il funzionamento di Angular2 DI.
Interrompe la compilazione offline che genera codice statico per sostituire DI dichiarativo e imperativo per migliorare le prestazioni e ridurre le dimensioni del codice.


4
Giusto per chiarire: ne ho bisogno OVUNQUE. Sto cercando di spostare quella dipendenza nella mia super classe in modo che OGNI classe derivata possa accedere al servizio senza la necessità di iniettarla individualmente in ogni classe derivata.
maxhb

9
La risposta alla sua stessa domanda è un brutto trucco. La domanda dimostra già come dovrebbe essere fatto. Ho elaborato un po 'di più.
Günter Zöchbauer

7
Questa risposta è corretta. L'OP ha risposto alla propria domanda ma ha infranto molte convenzioni. Anche il fatto che tu abbia elencato gli svantaggi effettivi è utile e lo garantisco - stavo pensando la stessa cosa.
dudewad

6
Voglio davvero (e continuo a) usare questa risposta per "hack" dell'OP. Ma devo dire che questo sembra tutt'altro che SECCO ed è molto doloroso quando voglio aggiungere una dipendenza nella classe base. Ho solo dovuto aggiungere iniezioni di ctor (e le superchiamate corrispondenti ) a circa 20+ classi e quel numero crescerà solo in futuro. Quindi due cose: 1) mi dispiacerebbe vedere un "grande codebase" fare questo; e 2) Grazie a Dio per vim qe vscodectrl+.

5
Solo perché è scomodo non significa che sia una cattiva pratica. I costruttori sono scomodi perché è molto difficile ottenere un'inizializzazione degli oggetti eseguita in modo affidabile. Direi che la pratica peggiore è costruire un servizio che necessita di "una classe base che inietta 15 servizi ed è ereditato da 6".
Günter Zöchbauer

64

Soluzione aggiornata, impedisce la generazione di più istanze di myService utilizzando l'iniettore globale.

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();
  }
}

Ciò garantirà che MyService possa essere utilizzato all'interno di qualsiasi classe che estende AbstractComponent senza la necessità di iniettare MyService in ogni classe derivata.

Ci sono alcuni svantaggi di questa soluzione (vedi il commento di @ Günter Zöchbauer sotto la mia domanda originale):

  • L'iniezione dell'iniettore globale è solo un miglioramento quando ci sono diversi servizi diversi che devono essere iniettati in molti luoghi. Se hai solo un servizio condiviso, probabilmente è meglio / più facile iniettare quel servizio all'interno delle classi derivate
  • La mia soluzione e la sua alternativa proposta hanno lo svantaggio di rendere più difficile vedere quale classe dipende da quale servizio.

Per una spiegazione molto ben scritta dell'iniezione di dipendenza in Angular2, vedere questo post del blog che mi ha aiutato molto a risolvere il problema: http ://blog. Thoughtram.io/angular/2015/05/18/dependency-injection-in-angular- 2.html


7
Ciò rende piuttosto difficile capire quali servizi vengono effettivamente iniettati.
Simon Dufour,

Non dovrebbe essere this.myServiceA = injector.get(MyServiceA);ecc.?
evento jenson-button

9
La risposta di @Gunter Zochbauer è quella corretta. Questo non è il modo corretto per farlo e infrange molte convenzioni angolari. Potrebbe essere più semplice in quanto la codifica di tutte quelle chiamate injection è un "dolore", ma se vuoi sacrificare la necessità di scrivere il codice del costruttore per essere in grado di mantenere una grande base di codice, allora ti stai sparando ai piedi. Questa soluzione non è scalabile, IMO, e causerà molti bug confusi lungo la strada.
dudewad

3
Non c'è il rischio di più istanze dello stesso servizio. Devi semplicemente fornire un servizio alla radice della tua applicazione per prevenire più istanze che potrebbero verificarsi su diversi rami dell'applicazione. Il passaggio di servizi alla modifica dell'ereditarietà non crea nuove istanze delle classi. La risposta di @Gunter Zochbauer è corretta.
ktamlyn

@maxhb hai mai esplorato l'estensione a Injectorlivello globale per evitare di dover concatenare parametri a AbstractComponent? Peraltro, penso che la proprietà che inietta dipendenze in una classe base ampiamente utilizzata per evitare il disordinato concatenamento di costruttori sia un'eccezione perfettamente valida alla regola usuale.
quentin-starin

4

Invece di iniettare manualmente tutti i servizi, ho creato una classe che fornisce i servizi, ad esempio, ottiene i servizi iniettati. Questa classe viene quindi iniettata nelle classi derivate e trasmessa alla classe base.

Classe derivata:

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

Classe base:

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

Classe di fornitura di servizi:

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

3
Il problema qui è che rischi di creare un servizio "junk drawer" che essenzialmente è solo un proxy per il servizio Injector.
kpup

1

Invece di iniettare un servizio che ha tutti gli altri servizi come dipendenze, in questo modo:

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

Salterei questo passaggio aggiuntivo e aggiungo semplicemente iniettare tutti i servizi in BaseComponent, in questo modo:

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

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

Questa tecnica presuppone 2 cose:

  1. La tua preoccupazione è interamente correlata all'ereditarietà dei componenti. Molto probabilmente, il motivo per cui sei arrivato a questa domanda è a causa dell'enorme quantità di codice non secco (WET?) Che devi ripetere in ogni classe derivata. Se desideri beneficiare di un unico punto di ingresso per tutti i tuoi componenti e servizi , dovrai eseguire il passaggio aggiuntivo.

  2. Ogni componente estende il BaseComponent

C'è anche uno svantaggio se decidi di utilizzare il costruttore di una classe derivata, poiché dovrai chiamare super()e passare tutte le dipendenze. Anche se non vedo davvero un caso d'uso che richieda l'uso di constructorinvece di ngOnInit, è del tutto possibile che esista un tale caso d'uso.


2
La classe base ha quindi dipendenze da tutti i servizi di cui ha bisogno uno dei suoi figli. ChildComponentA ha bisogno di ServiceA? Bene, ora ChildComponentB ottiene anche ServiceA.
Knallfrosch

0

Se la classe genitore è stata ottenuta da un plug-in di terze parti (e non è possibile modificare l'origine), puoi farlo:

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

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

o il modo migliore (rimani solo un parametro nel costruttore):

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

0

Da quello che ho capito per ereditare dalla classe base devi prima istanziarlo. Per istanziarlo è necessario passare i parametri richiesti dal suo costruttore, quindi li si passa da figlio a genitore tramite una chiamata super () in modo che abbia senso. L'iniettore ovviamente è un'altra soluzione praticabile.


0

BRUTTO HACK

Qualche tempo fa alcuni dei miei clienti vogliono unire due GRANDI progetti angolari a ieri (angular v4 in angular v8). Il progetto v4 utilizza la classe BaseView per ogni componente e contiene il tr(key)metodo per le traduzioni (in v8 uso ng-translate). Quindi, per evitare di cambiare sistema di traduzione e modificare centinaia di modelli (in v4) o impostare il sistema di traduzione 2 in parallelo, utilizzo il seguente brutto hack (non ne sono orgoglioso) - in AppModuleclasse aggiungo il seguente costruttore:

export class AppModule { 
    constructor(private injector: Injector) {
        window['UglyHackInjector'] = this.injector;
    }
}

e ora AbstractComponentpuoi usare

export class AbstractComponent {
  private myservice: MyService = null;

  constructor() {
    this.myservice = window['UglyHackInjector'].get(MyService);
  }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.