Ce este injecția de dependență?

Motivație

Injecția de dependență este adesea mai simplu denumită DI. Paradigma există în întregul Angular. Păstrează codul flexibil, testabil și modificabil. Clasele pot moșteni logica externă fără a ști cum să o creeze. Orice consumator din aceste clase nu trebuie să știe nimic.

DI salvează clasele și consumatorii deopotrivă de a trebui să știe mai mult decât este necesar. Cu toate acestea, codul este la fel de modular ca înainte, datorită mecanismelor care susțin DI în Angular.

Serviciile sunt un beneficiar cheie al DI. Se bazează pe paradigma pentru injecţie în diverși consumatori. Acești consumatori pot profita de serviciul respectiv și / sau îl pot transmite în altă parte.

Serviciile nu sunt singure. Directivele, conductele, componentele și așa mai departe: fiecare schemă din Angular beneficiază de DI într-un fel sau altul.

Injectoare

Injectoarele sunt structuri de date care stochează instrucțiuni care detaliază unde și cum se formează serviciile. Aceștia acționează ca intermediari în cadrul sistemului DI angular.

Modulul, directiva și clasele de componente conțin metadate specifice injectoarelor. O nouă instanță de injector însoțește fiecare dintre aceste clase. În acest fel, arborele aplicației reflectă ierarhia sa de injectoare.

providers: [] metadatele acceptă servicii care apoi se înregistrează la injectorul clasei. Acest câmp furnizor adaugă instrucțiunile necesare funcționării unui injector. O clasă (presupunând că are dependențe) creează un serviciu prin preluarea clasei sale ca tip de date. Injectorul aliniază acest tip a creează o instanță a acelui serviciu în numele clasei.

Desigur, clasa poate să instanțeze doar pentru ce are instrucțiunile injectorului. În cazul în care injectorul propriu al clasei nu are serviciul înregistrat, atunci îl interogă pe părintele său. Așa mai departe și așa mai departe până când ajungeți la un injector cu serviciul sau la rădăcina aplicației.

Serviciile se pot înregistra la orice injector din cadrul aplicației. Serviciile merg în providers: [] câmpul de metadate al modulelor, directivelor sau componentelor clasei. Copiii clasei pot instanția un serviciu înregistrat în injectorul clasei. La urma urmei, injectoarele pentru copii se încadrează în injectoarele părinte.

Injecție de dependență

Aruncați o privire asupra scheletelor pentru fiecare clasă: serviciu, modul, directivă și componentă.

// service

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

@Injectable({
  providedIn: /* injector goes here */
})
export class TemplateService {
  constructor() { }
}
// module

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: [],
  providers: [ /* services go here */ ]
})
export class TemplateModule { }
// directive

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

@Directive({
  selector: '[appTemplate]',
  providers: [ /* services go here */ ]
})
export class TemplateDirective {
  constructor() { }
}
//component

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

@Component({
  selector: 'app-template',
  templateUrl: './template.component.html',
  styleUrls: ['./template.component.css'],
  providers: [ /* services go here */ ]
})
export class TemplateComponent {
  // class logic ...
}

Fiecare schelet poate înregistra servicii către un injector. De fapt, TemplateService este un serviciu. Începând cu Angular 6, serviciile se pot înregistra acum cu injectoare folosind @Injectable metadate.

In orice caz

Observați providedIn: string (@Injectable) și providers: [] (@Directive, @Componet și @Module) metadate. Ei spun injectoarelor unde și cum să creeze un serviciu. Altfel, injectoarele nu ar ști cum să instanțeze.

Ce se întâmplă dacă un serviciu are dependențe? Unde ar merge rezultatele? Furnizorii răspund la acea întrebare, astfel încât injectoarele să poată crea instantanee corect.

Injectorii formează coloana vertebrală a cadrului DI. Acestea stochează instrucțiuni pentru instanțierea serviciilor, astfel încât consumatorii să nu fie obligați. Ei primesc instanțe de serviciu fără a fi nevoie să știe nimic despre dependența sursă!

De asemenea, ar trebui să rețin că alte scheme fără injectoare pot utiliza în continuare injecția de dependență. Nu pot înregistra servicii suplimentare, dar pot face instanțe de la injectoare.

Serviciu

providedIn: string metadate ale @Injectable specifică la ce injector să se înregistreze. Folosind această metodă și în funcție de utilizarea serviciului, serviciul se poate înregistra sau nu la injector. Angular o numește scuturarea copacilor.

În mod implicit, valoarea este setată la ‘root’. Acest lucru se traduce prin injectorul rădăcină al aplicației. Practic, setarea câmpului la ‘root’ face serviciul disponibil oriunde.

Notă rapidă

După cum s-a menționat anterior, injectoarele pentru copii se încadrează în părinții lor. Această strategie de rezervă asigură că părinții nu trebuie să se înregistreze din nou pentru fiecare injector. Consultați acest articol pe Servicii și injectoare pentru o ilustrare a acestui concept.

Serviciile înregistrate sunt singletoni. Adică, instrucțiunile pentru instanțierea serviciului există pe un singur injector. Aceasta presupune că nu a fost înregistrată în mod explicit în altă parte.

Modul, directivă și componentă

Modulele și componentele au fiecare propria instanță de injector. Acest lucru este evident având în vedere providers: [] câmp metadate. Acest câmp preia o serie de servicii și le înregistrează la injectorul modulului sau clasei de componente. Această abordare se întâmplă în @NgModule, @Directive, sau @Component decoratori.

Această strategie omite scuturarea copacilor, sau eliminarea opțională a serviciilor neutilizate din injectoare. Instanțele de service trăiesc pe injectorele lor pe toată durata modulului sau componentei.

Referințe instantanee

Referințele la DOM pot crea instanțe din orice clasă. Rețineți că referințele sunt încă servicii. Ele diferă de serviciile tradiționale prin reprezentarea stării altceva. Aceste servicii includ funcții pentru a interacționa cu referința lor.

Directivele au nevoie constantă de referințe DOM. Directivele efectuează mutații asupra elementelor gazdă prin aceste referințe. Vezi următorul exemplu. Injectorul directivei instanțiază o referință a elementului gazdă în constructorul clasei.

// directives/highlight.directive.ts

import { Directive, ElementRef, Renderer2, Input } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  constructor(
    private renderer: Renderer2,
    private host: ElementRef
  ) { }

  @Input() set appHighlight (color: string) {
    this.renderer.setStyle(this.host.nativeElement, 'background-color', color);
  }
}
// app.component.html

<p [appHighlight]="'yellow'">Highlighted Text!</p>

Renderer2 devine instantaneu. Din ce injector provin aceste servicii? Ei bine, codul sursă al fiecărui serviciu provine @angular/core. Aceste servicii trebuie apoi să se înregistreze la injectorul rădăcină al aplicației.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HighlightDirective } from './directives/highlight.directive';

@NgModule({
  declarations: [
    AppComponent,
    HighlightDirective
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [
    AppComponent
  ]
})
export class AppModule { }

O matrice de furnizori goi !? Să nu se teamă. Unghiular înregistrează multe servicii cu injectorul rădăcină automat. Aceasta include ElementRef și Renderer2. În acest exemplu, gestionăm elementul gazdă prin interfața sa care rezultă din instanțierea ElementRef. Renderer2 ne permite să actualizăm DOM prin modelul de vizualizare Angular.

Puteți citi mai multe despre vizualizări din Acest articol. Acestea sunt metoda preferată pentru actualizările DOM / vizualizare în aplicațiile angulare.

Este important să recunoaștem rolul pe care îl joacă injectoarele în exemplul de mai sus. Prin declararea tipurilor de variabile în constructor, clasa obține servicii valoroase. Tipul de date al fiecărui parametru se mapează la un set de instrucțiuni din injector. Dacă injectorul are acel tip, returnează o instanță de tipul respectiv.

Servicii instantanee

Servicii și injectoare articolul explică într-o anumită măsură această secțiune. Cu toate acestea, această secțiune reface secțiunea anterioară sau cea mai mare parte. Serviciile oferă adesea referințe la altceva. La fel de bine pot oferi o interfață care extinde capacitățile unei clase.

Următorul exemplu va defini un serviciu de înregistrare care se adaugă la injectorul unei componente prin intermediul acestuia providers: [] metadate.

// services/logger.service.ts

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

@Injectable()
export class LoggerService {
  callStack: string[] = [];

  addLog(message: string): void {
    this.callStack = [message].concat(this.callStack);
    this.printHead();
  }

  clear(): void {
    this.printLog();
    this.callStack = [];
    console.log(“DELETED LOG”);
  }

  private printHead(): void {
    console.log(this.callStack[0] || null);
  }

  private printLog(): void {
    this.callStack.reverse().forEach((log) => console.log(message));
  }
}
// app.component.ts

import { Component } from '@angular/core';
import { LoggerService } from './services/logger.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  providers: [LoggerService]
})
export class AppComponent {
  constructor(private logger: LoggerService) { }

  logMessage(event: any, message: string): void {
    event.preventDefault();
    this.logger.addLog(`Message: ${message}`);
  }

  clearLog(): void {
    this.logger.clear();
  }
}
// app.component.html

<h1>Log Example</h1>
<form (submit)="logMessage($event, userInput.value)">
  <input #userInput placeholder="Type a message...">
  <button type="submit">SUBMIT</button>
</form>

<h3>Delete Logged Messages</h3>
<button type="button" (click)="clearLog()">CLEAR</button>

Concentrați-vă pe constructorul și metadatele AppComponent. Injectorul de componente primește instrucțiuni din câmpul de metadate al furnizorului care conține LoggerService. Injectorul știe apoi ce să instanțeze LoggerService de la solicitarea în constructor.

Parametrul constructor loggerService are tipul LoggerService pe care injectorul o recunoaște. Injectorul continuă cu instanțierea așa cum s-a menționat.

Concluzie

Injecția de dependență (DI) este o paradigmă. Modul în care funcționează în Angular este printr-o ierarhie de injectori. O clasă își primește resursele fără a fi nevoie să le creeze sau să le cunoască. Injectorii primesc instrucțiuni și instanțiază un serviciu în funcție de care a fost solicitat.

DI apare foarte mult în Angular. Documentația oficială Angular explică de ce paradigma este atât de răspândită. De asemenea, continuă să descrie numeroasele cazuri de utilizare pentru DI în mod unghiular dincolo de ceea ce a fost discutat în acest articol. Verificați-l făcând clic mai jos!

Mai multe despre injectarea dependenței:

  • Introducere în injecția de dependență angulară
  • Introducere rapidă la injectarea dependenței