Spec-Zone.ru › Angular 2

Иерархические инжекторы зависимостей

Система иерархического инъектирования зависимостей Angular поддерживает вложенные инжекторы параллельно с деревом компонентов.

Вы изучили основы инъекции зависимостей Angular в руководстве Инъекция зависимостей.

Angular имеет систему иерархической инъекции зависимостей. На самом деле существует дерево инжекторов, которое параллельно дереву компонентов приложения. Вы можете переконфигурировать инжекторы на любом уровне этого дерева компонентов.

Это руководство исследует эту систему и то, как использовать её на практике.

Попробуйте пример в реальном времени.

Дерево инжекторов

В руководстве Инъекция зависимостей вы узнали, как настроить инъектор зависимостей и как получать зависимости, где они вам нужны.

На самом деле, такого понятия, как единственный инжектор, не существует. Приложение может иметь несколько инжекторов. Приложение Angular представляет собой дерево компонентов. Каждый экземпляр компонента имеет свой собственный инжектор. Дерево компонентов параллельно дереву инжекторов.

Инжектор компонента может быть прокси для инжектора предка, расположенного выше в дереве компонентов. Это реализационная деталь, повышающая эффективность. Вы не заметите разницы, и вашей ментальной моделью должно быть то, что каждый компонент имеет свой собственный инжектор.

Рассмотрим этот вариант руководства по приложению Tour of Heroes. Вверху находится AppComponent, который имеет некоторые дочерние компоненты. Один из них — HeroesListComponent. HeroesListComponent хранит и управляет несколькими экземплярами HeroTaxReturnComponent. Следующая диаграмма представляет состояние дерева компонентов этого руководства с тремя уровнями, когда одновременно открыты три экземпляра HeroTaxReturnComponent.

injector tree

Поднятие запросов инжектора

Когда компонент запрашивает зависимость, Angular пытается удовлетворить эту зависимость с помощью поставщика, зарегистрированного в инжекторе этого компонента. Если инжектор компонента не имеет поставщика, он передаёт запрос инжектору родительского компонента. Если этот инжектор не может удовлетворить запрос, он передаёт его своему родительскому инжектору. Запросы продолжают подниматься, пока Angular не найдёт инжектор, который может обработать запрос, или не исчерпаются инжекторы предков. Если предков не останется, Angular выбросит ошибку.

Вы можете ограничить поднятие запроса. Промежуточный компонент может объявить, что он является «хост» компонентом. Поиск поставщиков будет подниматься не выше инжектора этого хост-компонента. Это тема для другого дня.

Повторная регистрация службы на разных уровнях

Вы можете повторно зарегистрировать поставщика для определённого маркера зависимости на нескольких уровнях дерева инжекторов. Вам не обязательно повторно регистрировать поставщиков. Не следует этого делать, если у вас нет веской причины. Но вы можете это сделать.

Поскольку логика разрешения работает вверх, первый встреченный поставщик выигрывает. Таким образом, поставщик в промежуточном инжекторе перехватывает запрос на службу от чего-то более низкого в дереве. Он фактически «переконфигурирует» и «затеняет» поставщика на более высоком уровне в дереве.

Если вы укажете поставщиков только на верхнем уровне (обычно в корневом AppModule), дерево инжекторов, кажется, плоским. Все запросы поднимаются до корневого NgModule инжектора, который вы настроили с помощью метода bootstrapModule.

Инжекторы компонентов

Возможность настроить одного или несколько поставщиков на разных уровнях открывает интересные и полезные возможности.

Сценарий: изоляция службы

Архитектурные причины могут привести к ограничению доступа к службе областью приложения, к которой она относится.

В примере руководства есть VillainsListComponent, который отображает список злодеев. Он получает этих злодеев из VillainsService.

Хотя вы могли предоставить VillainsService в корневом AppModule (там вы найдёте HeroesService), это сделало бы VillainsService доступным повсюду в приложении, включая рабочие процессы героев.

Если вы впоследствии измените VillainsService, вы могли бы сломать что-то в компоненте героя где-то. Этого не должно происходить, но предоставление службы в корневом AppModule создаёт этот риск.

Вместо этого предоставьте VillainsService в метаданных providers компонента VillainsListComponent следующим образом:

src/app/villains-list.component.ts (метаданные)

@Component({
  selector: 'villains-list',
  templateUrl: './villains-list.component.html',
  providers: [ VillainsService ]
})

Предоставляя VillainsService в метаданных VillainsListComponent и нигде больше, служба становится доступной только в VillainsListComponent и его дереве дочерних компонентов. Это всё ещё синглтон, но это синглтон, существующий только в области злодеев.

Теперь вы знаете, что компонент героя не может получить к нему доступ. Вы снизили риск ошибок.

Сценарий: несколько сессий редактирования

Многие приложения позволяют пользователям работать над несколькими открытыми задачами одновременно. Например, в приложении для подготовки налоговых деклараций налоговый консультант может работать над несколькими декларациями, переключаясь с одной на другую в течение дня.

Это руководство демонстрирует этот сценарий на примере темы Tour of Heroes. Представьте себе внешний HeroListComponent, который отображает список супергероев.

Чтобы открыть налоговую декларацию героя, налоговый консультант нажимает на имя героя, что открывает компонент для редактирования этой декларации. Каждая выбранная налоговая декларация героя открывается в своём компоненте, и несколько деклараций могут быть открыты одновременно.

Каждый компонент налоговой декларации обладает следующими характеристиками:

  • Это собственная сессия редактирования налоговой декларации.
  • Может изменить налоговую декларацию, не затрагивая декларацию в другом компоненте.
  • Имеет возможность сохранить изменения в своей налоговой декларации или отменить их.
Heroes in action

Можно предположить, что HeroTaxReturnComponent имеет логику для управления и восстановления изменений. Это было бы довольно простой задачей для простой налоговой декларации героя. В реальном мире, с богатой моделью данных налоговой декларации, управление изменениями будет сложным. Вы можете делегировать это управление вспомогательной службе, как это сделано в данном примере.

Вот HeroTaxReturnService. Она кэширует единственный HeroTaxReturn, отслеживает изменения в этой декларации и может сохранить или восстановить её. Также она делегирует работу приложению-глобальному синглтону HeroService, который она получает посредством инъекции.

src/app/hero-tax-return.service.ts

import { Injectable }    from '@angular/core';
import { HeroTaxReturn } from './hero';
import { HeroesService } from './heroes.service';

@Injectable()
export class HeroTaxReturnService {
  private currentTaxReturn: HeroTaxReturn;
  private originalTaxReturn: HeroTaxReturn;

  constructor(private heroService: HeroesService) { }

  set taxReturn (htr: HeroTaxReturn) {
    this.originalTaxReturn = htr;
    this.currentTaxReturn  = htr.clone();
  }

  get taxReturn (): HeroTaxReturn {
    return this.currentTaxReturn;
  }

  restoreTaxReturn() {
    this.taxReturn = this.originalTaxReturn;
  }

  saveTaxReturn() {
    this.taxReturn = this.currentTaxReturn;
    this.heroService.saveTaxReturn(this.currentTaxReturn).subscribe();
  }
}

Вот HeroTaxReturnComponent , которая использует её.

src/app/hero-tax-return.component.ts

import { Component, EventEmitter, Input, Output } from '@angular/core';
import { HeroTaxReturn }        from './hero';
import { HeroTaxReturnService } from './hero-tax-return.service';

@Component({
  selector: 'hero-tax-return',
  templateUrl: './hero-tax-return.component.html',
  styleUrls: [ './hero-tax-return.component.css' ],
  providers: [ HeroTaxReturnService ]
})
export class HeroTaxReturnComponent {
  message = '';
  @Output() close = new EventEmitter<void>();

  get taxReturn(): HeroTaxReturn {
    return this.heroTaxReturnService.taxReturn;
  }
  @Input()
  set taxReturn (htr: HeroTaxReturn) {
    this.heroTaxReturnService.taxReturn = htr;
  }

  constructor(private heroTaxReturnService: HeroTaxReturnService ) { }

  onCanceled()  {
    this.flashMessage('Canceled');
    this.heroTaxReturnService.restoreTaxReturn();
  };

  onClose()  { this.close.emit(); };

  onSaved() {
    this.flashMessage('Saved');
    this.heroTaxReturnService.saveTaxReturn();
  }

  flashMessage(msg: string) {
    this.message = msg;
    setTimeout(() => this.message = '', 500);
  }
}

Налоговая-декларация-для-редактирования поступает через свойство ввода, которое реализовано с помощью геттеров и сеттеров. Сеттер инициализирует собственный экземпляр HeroTaxReturnService компонента с поступающей декларацией. Геттер всегда возвращает то, что служба считает текущим состоянием героя. Компонент также просит службу сохранить и восстановить эту налоговую декларацию.

Была бы большая проблема, если бы эта служба была приложением-глобальным синглтоном. Каждый компонент разделял бы один и тот же экземпляр службы. Каждый компонент перезаписывал бы налоговую декларацию, которая принадлежала другому герою. Что за беспорядок!

Внимательно изучите метаданные HeroTaxReturnComponent. Обратите внимание на свойство providers.

providers: [ HeroTaxReturnService ]

У HeroTaxReturnComponent есть свой собственный поставщик HeroTaxReturnService. Помните, что каждый экземпляр компонента имеет свой собственный инжектор. Предоставление службы на уровне компонента гарантирует, что каждый экземпляр компонента получает свой собственный, частный экземпляр службы. Никакого перезаписивания налоговых деклараций. Никакого беспорядка.

Остальная часть кода сценария зависит от других функций и методов Angular, о которых вы можете узнать в других разделах документации. Вы можете просмотреть и загрузить его из примера в реальном времени.

Сценарий: специализированные поставщики

Ещё одна причина для повторной регистрации службы — замена более специализированной реализации этой службы, глубже в дереве компонентов.

Ещё раз рассмотрим пример с автомобилем из руководства Инъекция зависимостей. Предположим, вы настроите корневой инжектор (отмеченный как A) со стандартными поставщиками для CarService, EngineService и TiresService.

Вы создаёте компонент автомобиля (A), который отображает автомобиль, построенный из этих трёх стандартных служб.

Затем вы создаёте дочерний компонент (B), который определяет свои собственные, специализированные поставщики для CarService и EngineService , обладающие специальными возможностями, подходящими для того, что происходит в компоненте (B).

Компонент (B) является родителем другого компонента (C), который определяет свой собственный, ещё более специализированный поставщик для CarService.

car components

За кулисами каждый компонент настраивает свой собственный инжектор с нулевыми, одним или несколькими поставщиками, определёнными для самого компонента.

Когда вы разрешаете экземпляр Car в самом глубоком компоненте (C), его инжектор создаёт экземпляр Car , разрешённый инжектором (C), с Engine , разрешённым инжектором (B), и Tires , разрешённым корневым инжектором (A).

car injector tree

Код для этого сценария с автомобилями находится в файлах car.components.ts и car.services.ts примера, которые вы можете просмотреть и загрузить из примера в реальном времени.

© 2010–2017 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://v2.angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API