Інжекція кутової залежності, пояснена на прикладах

Що таке ін’єкція залежності?

Мотивація

Ін’єкцію залежності часто простіше називають DI. Парадигма існує в усьому Angular. Він зберігає код гнучким, перевіряється та змінюється. Класи можуть успадкувати зовнішню логіку, не знаючи, як її створити. Будь-які споживачі цих класів також не повинні нічого знати.

DI рятує класи і споживачів від необхідності знати більше, ніж потрібно. Проте код такий же модульний, як і раніше, завдяки механізмам, що підтримують DI в Angular.

Послуги є ключовим благодійником DI. Вони покладаються на парадигму введення для різних споживачів. Потім ці споживачі можуть скористатися цією послугою та / або переслати її в інше місце.

Служба не одна. Директиви, труби, компоненти тощо: кожна схема в Angular має певні переваги від 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визначає , який Injector реєструватися. За допомогою цього методу та залежно від того, чи послуга використовується, служба може або не зареєструватися в інжекторі. Angular називає це тремтінням дерев .

За замовчуванням значенням встановлено значення ‘root’. Це перекладається на кореневий інжектор програми. В основному, установка поля ‘root’робить послугу доступною де завгодно.

Швидка примітка

Як уже зазначалося, дитячі ін'єктори відстають від батьків. Ця резервна стратегія гарантує, що батькам не потрібно перереєструватися для кожного інжектора. Зверніться до цієї статті про послуги та інжектори для ілюстрації цієї концепції.

Зареєстровані послуги є одинаками . Це означає, що інструкції щодо створення екземпляра послуги існують лише на одній форсунці. Це передбачає, що воно не було явно зареєстровано в іншому місці.

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

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

Ця стратегія не включає тремтіння дерев або необов’язкове видалення невикористаних служб із інжекторів. Екземпляри служб живуть на своїх форсунках протягом усього терміну експлуатації модуля або компонента.

Інстанціація посилань

Посилання на DOM можуть створюватися з будь-якого класу. Майте на увазі, що посилання все ще є послугами. Вони відрізняються від традиційних служб тим, що представляють стан чогось іншого. Ці послуги включають функції взаємодії з їх посиланням.

Директиви постійно потребують посилань на DOM. Директиви виконують мутації елементів-хостів за допомогою цих посилань. Дивіться наступний приклад. Інжектор директиви створює посилання на хост-елемент у конструкторі класу.

// 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також отримує примірник. З якого інжектора походять ці послуги? Що ж, вихідний код кожної служби походить з @angular/core. Потім ці служби повинні зареєструватися в кореневому інжекторі програми.

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

Порожній масив провайдерів !? Не боятися. Angular реєструє багато служб за допомогою кореневого інжектора автоматично. Сюди входить ElementRefі Renderer2. У цьому прикладі ми управляємо елементом хосту через його інтерфейс, що випливає з екземпляра ElementRef. Renderer2дозволяє нам оновити DOM через модель подання Angular.

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