Инжектиране на ъглова зависимост, обяснено с примери

Какво е инжектиране на зависимост?

Мотивация

Инжектирането на зависимост често се нарича по-просто DI. Парадигмата съществува в целия Angular. Той поддържа код гъвкав, тестваем и променлив. Класовете могат да наследят външната логика, без да знаят как да я създадат. Всички потребители от тези класове също не трябва да знаят нищо.

DI спасява класовете и потребителите от необходимостта да знаят повече от необходимото. И все пак кодът е толкова модулен, колкото и преди, благодарение на механизмите, поддържащи DI в Angular.

Услугите са ключов благодетел на DI. Те разчитат на парадигмата за инжектиране при различни потребители. След това тези потребители могат да се възползват от предоставената услуга и / или да я препратят другаде.

Сервизът не е сам. Директиви, тръби, компоненти и така нататък: всяка схема в Ъгловата полза от DI по някакъв начин.

Инжектори

Инжекторите са структури от данни, които съхраняват инструкции, описващи подробно къде и как се формират услугите. Те действат като посредници в системата Angular DI.

Класовете модули, директиви и компоненти съдържат метаданни, специфични за инжекторите. Нов екземпляр на инжектор придружава всеки един от тези класове. По този начин дървото на приложенията отразява йерархията на инжекторите.

В providers: []метаданни приема услуги, които след това се регистрират в инжектора на класа ". Това поле на доставчика добавя инструкциите, необходими за функционирането на инжектор. Клас (ако приемем, че има зависимости) създава екземпляр на услуга, като приема своя клас като свой тип данни. Инжекторът подравнява този тип а, създава екземпляр на тази услуга от името на класа.

Разбира се, класът може само да инстанцира това, за което инжекторът има инструкции. Ако собственият инжектор на класа няма регистрирана услуга, тогава той отправя запитване към своя родител. Така нататък и така нататък, докато не достигнете инжектор с услугата или корен на приложението.

Услугите могат да се регистрират на всеки инжектор в приложението. Услугите отиват в полето с providers: []метаданни на модули, директиви или компоненти на класа. Децата на класа могат да създадат екземпляр на услуга, регистрирана в инжектора на класа. В края на краищата детските инжектори отстъпват на родителските инжектори.

Инжектиране на зависимост

Погледнете скелетите за всеки клас: услуга, модул, директива и компонент.

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

Всеки скелет може да регистрира услуги на инжектор. Всъщност TemplateService е услуга. От Angular 6 услугите вече могат да се регистрират с инжектори, използвайки @Injectableметаданни.

Във всеки случай

Забележете метаданните providedIn: string( @Injectable) и providers: []( @Directive, @Componetи @Module). Те казват на инжекторите къде и как да създадат услуга. В противен случай инжекторите не биха знаели как да създават инстанции.

Ами ако услугата има зависимости? Къде биха отишли ​​резултатите? Доставчиците отговарят на тези въпроси, така че инжекторите да могат правилно да създават екземпляри.

Инжекторите формират гръбнака на DI рамката. Те съхраняват инструкции за екземпляри на услуги, така че потребителите да не трябва. Те получават екземпляри на услуги, без да е необходимо да знаят нищо за зависимостта от източника!

Трябва също да отбележа, че други схеми без инжектори все още могат да използват инжектиране на зависимост. Те не могат да регистрират допълнителни услуги, но все пак могат да създават екземпляри от инжектори.

Обслужване

В providedIn: stringметаданни от @Injectableпосочват за кои инжектор да се регистрират. Използвайки този метод и в зависимост от това дали услугата се използва, услугата може или не може да се регистрира с инжектора. Angular нарича това разклащане на дървета .

По подразбиране стойността е зададена на ‘root’. Това се превръща в основния инжектор на приложението. По принцип задаването на полето ‘root’прави услугата достъпна навсякъде.

Бърза бележка

Както бе споменато по-горе, детските инжектори отстъпват на своите родители. Тази резервна стратегия гарантира, че родителите не трябва да се регистрират отново за всеки инжектор. Вижте тази статия за Услуги и инжектори за илюстрация на тази концепция.

Регистрираните услуги са единични . Това означава, че инструкциите за екземпляр на услугата съществуват само за един инжектор. Това предполага, че не е било изрично регистрирано другаде.

Модул, директива и компонент

Модулите и компонентите имат свой собствен инжектор. Това е очевидно предвид полето с providers: []метаданни. Това поле приема масив от услуги и ги регистрира с инжектора на класа модул или компонент. Този подход се случва в @NgModule, @Directiveили @Componentдекоратори.

Тази стратегия пропуска разклащането на дървета или незадължителното премахване на неизползвани услуги от инжектори. Екземплярите на услуги живеят от инжекторите си през целия живот на модула или компонента.

Инсталиране на референции

Препратките към DOM могат да бъдат създадени от всеки клас. Имайте предвид, че референциите все още са услуги. Те се различават от традиционните услуги по представяне на състоянието на нещо друго. Тези услуги включват функции за взаимодействие с референтната им информация.

Directives are in constant need of DOM references. Directives perform mutations on their host elements through these references. See the following example. The directive’s injector instantiates a reference of the host element into the class’ constructor.

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

Highlighted Text!

Renderer2 also gets instantiated. Which injector do these services come from? Well, each service’s source code comes from @angular/core. These services must then register with the application’s root injector.

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

An empty providers array!? Not to fear. Angular registers many services with the root injector automatically. This includes ElementRef and Renderer2. In this example, we are managing the host element through its interface stemming from the instantiation of ElementRef. Renderer2 lets us update the DOM through Angular’s view model.

You can read more about views from this article. They are the preferred method for DOM/view updates in Angular applications.

It is important recognize the role that injectors play in the above example. By declaring variable types in the constructor, the class obtains valuable services. Each parameter’s data type maps to a set of instructions within the injector. If the injector has that type, it returns an instance of said type.

Instantiating Services

The Services and Injectors article explains this section to an extent. Though, this section rehashes the previous section or the most part. Services will often provide references to something else. They may just as well provide an interface extending a class’ capabilities.

The next example will define a logging service that gets added to a component’s injector via its providers: [] metadata.

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

Log Example

SUBMIT

Delete Logged Messages

CLEAR

Focus on the AppComponent constructor and metadata. The component injector receives instructions from the provider’s metadata field containing LoggerService. The injector then knows what to instantiate LoggerService from requested in the constructor.

The constructor parameter loggerService has the type LoggerService which the injector recognizes. The injector follows through with the instantiation as mentioned.

Conclusion

Dependency injection (DI) is a paradigm. The way it works in Angular is through a hierarchy of injectors. A class receives its resources without having to create or know about them. Injectors receive instruction and instantiate a service depending on which one was requested.

DI shows up a lot in Angular. The official Angular documentation explains why the paradigm is so prevalent. They also go on to describe the numerous use-cases for DI in Angular way beyond what was discussed in this article. Check it out by clicking below!

More on dependency injection:

  • Intro to Angular dependency injection
  • Quick intro to dependency injection