При разработке проектов Angular мы обычно используем привязку свойств ввода и привязку событий вывода для взаимодействия компонентов. Однако ввод и вывод можно использовать только в родительско-дочернем режиме. компоненты передают информацию. Компоненты образуют дерево компонентов на основе отношения вызова. Если есть только привязки свойств и привязок событий, то два компонента непрямых отношений должны взаимодействовать через каждую точку соединения. Посреднику необходимо постоянно обрабатывать и передавать некоторые вещи, которые он делает. информацию знать не нужно (рис. 1 слева). Сервис Injectable, предоставляемый в Angular, может быть предоставлен в модулях, компонентах или инструкциях и в сочетании с внедрением в конструктор может решить эту проблему (прямо на рисунке 1). [Рекомендуемые связанные учебные пособия: «учебник по Angular»]
Рисунок 1 Модель связи компонентов
. Левое изображение передает информацию только через родительско-дочерние компоненты. Узлу a и узлу b необходимо пройти через множество узлов для связи; если узел c хочет управлять узлом b посредством некоторой конфигурации, узлы между ними также должны быть установлены; дополнительные атрибуты или события для прозрачной передачи соответствующей информации. Узел c режима внедрения зависимостей на рисунке справа может предоставлять услугу для связи узлов a и b. Узел a напрямую взаимодействует с услугой, предоставляемой узлом c, а узел b также напрямую взаимодействует с услугой, предоставляемой узлом c. Наконец, связь упрощается, и средний узел не связан с этой частью контента и не имеет очевидного понимания связи, которая происходит между верхним и нижним компонентами.
Внедрение зависимостей (DI) не является уникальным для Angular. Это средство реализации шаблона проектирования инверсии управления (IOC). Появление внедрения зависимостей решает проблему чрезмерного связывания ручного управления. создание экземпляров, и все ресурсы не могут быть использованы. Управление ресурсами обеими сторонами, а не предоставление их ресурсными центрами или третьими сторонами, которые не используют ресурсы, может принести много преимуществ. Во-первых, централизованное управление ресурсами делает ресурсы настраиваемыми и простыми в управлении. Во-вторых, это снижает степень зависимости между двумя сторонами, использующими ресурсы, — это то, что мы называем сцеплением.
Аналогия с реальным миром заключается в том, что когда мы покупаем такой продукт, как карандаш, нам нужно только найти магазин, где можно купить товар типа карандаша. Нам не важно, где производится карандаш или из какого дерева и грифеля карандаша. Они склеены. Нам это просто нужно для выполнения функции письма. У нас не будет никаких контактов с конкретным производителем или фабрикой карандашей. Что касается магазина, то он может приобретать карандаши в соответствующих каналах и реализовывать возможность настройки ресурсов.
В частности, в сочетании со сценарием кодирования пользователям не нужно явно создавать экземпляры (новые операции) для внедрения и использования экземпляров. Создание экземпляров определяется поставщиками. Управление ресурсами осуществляется через токены. Поскольку он не заботится о поставщике или создании экземпляров, пользователь может использовать некоторые локальные методы внедрения (вторичная конфигурация токенов), чтобы наконец добиться замены экземпляра и режима внедрения зависимостей. Приложения и аспектное программирование (AOP). ) дополняют друг друга.
Внедрение зависимостей — это один из наиболее важных основных модулей платформы Angular. Angular не только обеспечивает внедрение типов служб, но и само дерево компонентов представляет собой дерево зависимостей внедрения, а также могут быть внедрены функции и значения. То есть в среде Angular дочерние компоненты могут внедрять экземпляры родительского компонента через токен родительского компонента (обычно имя класса). При разработке библиотеки компонентов существует большое количество случаев, когда взаимодействие и связь достигаются путем внедрения родительских компонентов, включая монтирование параметров, совместное использование состояния и даже получение DOM узла, на котором расположен родительский компонент, и т. д.
Чтобы использовать Angular внедрение, вы должны сначала понять процесс разрешения зависимостей. Подобно процессу анализа node_modules, когда зависимости не найдены, зависимости всегда будут подниматься на родительский уровень для поиска зависимостей. Старая версия Angular (до версии 6) разделяет процесс анализа внедрения на многоуровневые инжекторы модулей, многоуровневые инжекторы компонентов и инжекторы элементов. Новая версия (после v9) упрощена до двухуровневой модели. Первая цепочка запросов — это статический инжектор элементов уровня DOM, инжектор компонентов и т. д., которые вместе называются инжекторами элементов, а другая цепочка запросов — это инжектор модулей. Порядок анализа и значение по умолчанию после сбоя анализа более подробно объяснены в официальном документе с комментариями к коду (provider_flag).
Рисунок 2. Процесс поиска зависимостей двухуровневого инжектора (источник изображения)
означает, что компоненты/инструкции и предоставление внедренного контента на уровне компонента/инструкции сначала будут искать зависимости в элементах в представлении компонента вплоть до корневого элемента. Если не найден, то в элементе Текущий модуль выполняется поиск ссылки (включая ссылку на модуль и ссылку на отложенную загрузку маршрутизации) родительского модуля до корневого модуля и модуля платформы.
Обратите внимание, что инжектор здесь имеет наследование. Инжектор элемента может создавать и наследовать функцию поиска инжектора родительского элемента, и инжектор модуля аналогичен. После непрерывного наследования это становится чем-то похожим на цепочку прототипов объектов js.
понимают приоритет порядка разрешения зависимостей, и мы можем предоставить контент на соответствующем уровне. Мы уже знаем, что он бывает двух типов: внедрение модуля и внедрение элемента.
Инжектор модуля: поставщики могут быть настроены в атрибуте метаданных @NgModule, а также вы можете использовать оператор @Injectable, указанный после того, как v6. ProvideIn объявлен как имя модуля, «root» и т. д. (На самом деле над корневым модулем есть два инжектора, Platform и Null. Они не будут здесь обсуждаться.)
Инжектор элемента: Провайдеры, viewProviders можно настроить в атрибуте метаданных компонента @Component или в @ директивы. провайдеры в метаданных директивы.
Кроме того, помимо использования объявленного модуля-инжектора, декоратор @Injectable также может быть объявлен как инжектор элемента. Чаще всего он будет объявлен как предоставленный в корне для реализации синглтона. Он интегрирует метаданные через сам класс, чтобы избежать прямого объявления провайдера модулями или компонентами. Таким образом, если у класса нет какой-либо службы директив компонента и других классов для его внедрения, не будет кода, связанного с объявлением типа, и не будет кода, связанного с объявлением типа. компилятор может игнорировать его, тем самым достигая встряхивания дерева.
Другой способ предоставить это значение — напрямую указать значение при объявлении InjectionToken.
Вот сокращенные шаблоны для этих методов:
@NgModule({ поставщики: [ //Инжектор модуля] }) класс экспорта MyModule {}
@Component({ поставщики: [ // инжектор элемента - компонент], Провайдеры просмотра: [ //Инжектор элементов — вид компонента] }) класс экспорта MyComponent {}
@Directive({ поставщики: [ // инжектор элемента - директива] }) класс экспорта MyDirective {}
@Injectable({ предоставлено: 'корень' }) класс экспорта MyService {}
экспорт константа MY_INJECT_TOKEN = новый InjectionToken<MyClass>('my-inject-token', { предоставлено: 'корень', фабрика: () => { вернуть новый MyClass(); } });
Различные варианты предоставления местоположений зависимостей приведут к некоторым различиям, которые в конечном итоге повлияют на размер пакета, область, в которую могут быть внедрены зависимости, и жизненный цикл зависимостей. Существуют различные применимые решения для разных сценариев, таких как одноэлементный (корневой), изоляция службы (модуль), несколько окон редактирования (компонент) и т. д. Следует выбрать разумное расположение, чтобы избежать недопустимой общей информации или избыточной упаковки кода.
обеспечивают только внедрение экземпляров, это не продемонстрирует гибкость внедрения зависимостей структуры Angular. Angular предоставляет множество гибких инструментов внедрения. useClass автоматически создает новые экземпляры, useValue использует статические значения, useExisting может повторно использовать существующие экземпляры, а useFactory создается с помощью функций с указанными параметрами конструктора. Эти комбинации могут быть очень универсальными. Вы можете отрезать токен класса и заменить его другим подготовленным вами экземпляром. Вы можете сначала создать токен, чтобы сохранить значение или экземпляр, а затем снова заменить его, когда вам понадобится использовать его позже. заводская функция для его возврата. Локальная информация экземпляра сопоставляется с другим объектом или значением атрибута. Геймплей здесь будет описан в следующих случаях, поэтому я не буду вдаваться в подробности здесь. На официальном сайте также есть множество примеров для справки.
Внедрение в Angular можно внедрить внутри конструктора, или вы можете получить инжектор для получения существующих внедренных элементов с помощью метода get.
Angular поддерживает добавление декораторов для маркировки при внедрении,
, есть статья «@Self или @Optional @Host? Визуальное руководство по декораторам Angular DI», в которой очень ярко показано, что если между родительскими и дочерними компонентами используются разные декораторы, в конечном итоге будут затронуты следующие примеры: Что за фигня? разница.
Рис. 3. Результаты фильтрации различных внедренных декораторов.
Среди декораторов представления хоста и @Host @Host может оказаться самым трудным для понимания. Вот несколько конкретных инструкций для @Host. Официальное объяснение декоратора @Host таково
: получение зависимости от любого инжектора до достижения элемента хоста.
Здесь хост означает, что декоратор @Host ограничит область запроса внутри элемента хоста. Что такое хост-элемент? Если компонент B является компонентом, используемым шаблоном компонента A, то экземпляр компонента A является основным элементом экземпляра компонента B. Содержимое, созданное шаблоном компонента, называется представлением. Одно и то же представление может быть разными представлениями для разных компонентов. Если компонент A использует компонент B в пределах своей собственной области шаблона (см. рис. 4), представление (часть красного прямоугольника), сформированное содержимым шаблона A, является встроенным представлением компонента A, а компонент B находится внутри этого представления, поэтому для B , это представление является представлением узла B. Декоратор @Host ограничивает область поиска представлением хоста. Если он не найден, он не появится.
Рис. 4. Встроенное представление и представление хоста
Давайте рассмотрим реальные случаи, чтобы увидеть, как работает внедрение зависимостей, как устранять ошибки и как играть.
Компонент модального окна библиотеки компонентов DevUI предоставляет службу ModalService, которая может открывать модальное окно и может быть настроена как пользовательский компонент. Студенты-бизнесмены часто сообщают об ошибках при использовании этого компонента, говоря, что пакет не может найти пользовательский компонент.
Например, сообщается о следующей ошибке:
Рисунок 5. При использовании ModalService возникает ошибка при создании компонента, ссылающегося на EditorX. Невозможно найти соответствующий поставщик услуг.
Проанализируйте, как ModalService создает пользовательские компоненты. Как видите, если componentFactoryResolver
не передан, используется componentFactoryResolver
, внедренный ModalService. В большинстве случаев компания вводит DevUIModule один раз в корневой модуль, но не добавляет ModalModule в текущий модуль. То есть текущая ситуация на рисунке 6 такая. Согласно рисунку 6, в инжекторе ModalService отсутствует EditorXModuleService.
Рисунок 6. Диаграмма взаимоотношений предоставления сервисов модуля
. В соответствии с наследованием инжектора существует четыре решения:
поместить EditorXModule там, где объявлен ModalModule, чтобы инжектор мог найти EditorModuleService, предоставляемый EditorXModule — это само по себе худшее решение. Реализована отложенная загрузка. с помощью loadChildren уменьшить загрузку модуля домашней страницы. В результате контент, который необходимо использовать на подстранице, помещается в AppModule. Большой текстовый модуль загружается при первой загрузке, что усугубляет ситуацию. FMP (Первая значимая краска).
Ввести ModalService в модуль, который представляет EditorXModule и использует ModalService — желательно. Есть только одна ситуация, которая нецелесообразна, а именно вызов ModalService — еще одного общедоступного сервиса верхнего уровня, который все равно помещает ненужные модули на верхний уровень для загрузки.
При запуске компонента с помощью ModalService внедрите componentFactoryResolver
текущего модуля и передайте его в параметр открытой функции ModalService — желательно ввести EditorXModule там, где он фактически используется.
В используемом модуле желательно вручную предоставить ModalService, который решает проблему внедрения поиска.
Эти четыре метода фактически решают проблему EditorXModuleService во внутренней цепочке инжектора componentFactoryResolver
используемого ModalService. Эту проблему можно решить, обеспечив двухуровневую цепочку поиска.
Краткое изложение знаний : наследование модуля-инжектора и область поиска.
Обычно, когда мы используем один и тот же шаблон в нескольких местах, мы извлекаем общую часть через шаблон. Когда компонент DevUI Select был разработан ранее, разработчик хотел извлечь общую часть и сообщил об ошибке. ошибка.
Рисунок 7. Ошибка перемещения кода и внедрения не обнаружена.
Это связано с тем, что инструкции CdkVirtualScrollFor необходимо внедрить CdkVirtualScrollViewport. Однако система наследования внедрения элементов наследует DOM статической связи AST, поэтому динамическая связь невозможна. происходит следующее поведение запроса, и поиск завершается неудачей.
Рис. 8. Диапазон поиска цепочки запросов Element Injector
Окончательное решение: либо 1) оставить исходную позицию кода без изменений, либо 2) вам нужно встроить весь шаблон, чтобы найти его.
Рис. 9. Внедрение всего модуля позволяет CdkVitualScrollFo найти CdkVirtualScrollViewport (Решение 2).
Краткое изложение основных моментов : Цепочка запросов инжектора элементов является предком элемента DOM статического шаблона.
Этот случай взят из этого блога «Angular: вложенная форма, управляемая шаблоном».
Мы также столкнулись с той же проблемой при использовании проверки формы. Как показано на рисунке 10, по некоторым причинам мы инкапсулируем адреса трех полей в компонент для повторного использования.
Рисунок 10: Инкапсуляция трех адресных полей формы в подкомпонент.
На этом этапе мы обнаружим, что ngModelGroup
требует ControlContainer
внутри хоста, который представляет собой содержимое, предоставляемое директивой ngForm.
Рисунок 11. ngModelGroup не может найти ControlContainer.
Глядя на код ngModelGroup, вы можете видеть, что он только добавляет ограничение декоратора хоста.
Рисунок 12. ng_model_group.ts ограничивает область внедрения ControlContainer.
Здесь вы можете использовать viewProvider с usingExisting, чтобы добавить поставщика ControlContainer в представление хоста AddressComponent.
Рис. 13. Использование viewProviders для предоставления внешних
знаний Provider для вложенных компонентов. Краткое изложение знаний: Прекрасное использование viewProvider и usingExisting.
ленивой загрузки, что приводит к невозможности перетаскивания друг друга. Внутренняя бизнес-платформа предполагает перетаскивание между несколькими модулями. при загрузке loadChildren каждый модуль будет DragDropModule библиотеки компонентов DevUI упакован отдельно, и этот модуль предоставляет DragDropService. Инструкции перетаскивания делятся на инструкции Draggable и Droppable. Эти две инструкции взаимодействуют через DragDropService. Первоначально можно было взаимодействовать, представив один и тот же модуль и используя службу, предоставляемую этим модулем. Однако после отложенной загрузки модуль DragDropModule был упакован дважды, что также привело к появлению двух изолированных экземпляров. В это время инструкция Draggable в модуле с отложенной загрузкой не может взаимодействовать с инструкцией Droppable в другом модуле с отложенной загрузкой, поскольку в данный момент DragDropService не является тем же экземпляром.
Рис. 14. Отложенная загрузка модулей приводит к тому, что сервисы не являются одним и тем же экземпляром/одиночным случаем.
Очевидно, что наше требование здесь состоит в том, что нам нужен синглтон, а метод синглтона обычно — providerIn: 'root'
. DragDropService библиотеки компонентов. Хорошо ли предоставлять корневой домен непосредственно на уровне модуля? Но если хорошенько подумать, здесь есть и другие проблемы. Сама библиотека компонентов предоставляется для использования различными компаниями. Если у некоторых компаний есть две соответствующие группы перетаскивания в двух местах на странице, они не хотят быть связаны. В это время синглтон разрушает естественную изоляцию, основанную на модуле.
Тогда было бы разумнее реализовать замену синглтона со стороны бизнеса. Помните о цепочке запросов зависимостей, о которой мы упоминали ранее. Сначала ищется инжектор элемента. Если он не найден, запускается инжектор модуля. Итак, идея замены состоит в том, что мы можем предоставить поставщиков на уровне элементов.
Рис. 15. Используйте метод расширения, чтобы получить новый DragDropService и пометить его как предоставленный на корневом уровне.
Рисунок16.
Вы можете использовать тот же селектор для наложения повторяющихся инструкций, наложения дополнительной инструкции на инструкцию Draggable и инструкцию Droppable библиотеки компонентов и замены токена DragDropService на DragDropGlobalService, который предоставил одноэлементный элемент в корне.
На рисунках 15 и 16 мы внедряем элементы. Обработчик накладывает инструкции и заменяет токен DragDropService экземпляром нашего собственного глобального синглтона. На данный момент, когда нам нужно использовать глобальный одноэлементный DragDropService, нам нужно только представить модуль, который объявляет и экспортирует эти две дополнительные инструкции, чтобы позволить инструкции Draggable Droppable библиотеки компонентов взаимодействовать между отложенно загружаемыми модулями.
Краткое изложение знаний : Инжекторы элементов имеют более высокий приоритет, чем инжекторы модулей.
Тематика библиотеки компонентов DevUI использует пользовательские атрибуты CSS (переменные CSS) для объявления значения корневой переменной CSS для переключения тем. . Если мы хотим отображать предварительные просмотры разных тем одновременно в одном интерфейсе, мы можем переопределить переменные CSS локально в элементе DOM, чтобы добиться функции локальных тем. Когда я раньше создавал генератор дизеринга темы, я использовал этот метод для локального применения темы.
Рисунок 17. Функция локальной темы
, но недостаточно применить значения переменных CSS локально. Есть несколько раскрывающихся всплывающих слоев, которые по умолчанию прикреплены к задней части тела, а это означает, что их слой прикрепления находится снаружи. локальные переменные, что приведет к очень неловкой ситуации. В раскрывающемся списке компонента локальной темы отображается стиль внешней темы.
Рис. 18. Компонент локальной темы прикреплен к раскрывающемуся списку внешнего наложения.
Что делать, если тема неверна? Нам следует переместить точку присоединения обратно в локальный домен темы.
Известно, что Overlay компонента DatePickerPro библиотеки компонентов DevUI использует Overlay Angular CDK. После раунда анализа мы заменили его внедрением следующим образом:
1) Сначала мы наследуем OverlayContainer и реализуем собственный ElementOverlayContainer, как показано. ниже.
Рис. 19. Настройте ElementOverlayContainer и замените логику _createContainer.
2) Затем напрямую предоставьте наш новый ElementOverlayContainer на стороне компонента предварительного просмотра и предоставьте новый Overlay, чтобы новый Overlay мог использовать наш OverlayContainer. Изначально Overlay и OverlayContainer предоставляются в корневом каталоге, здесь нам нужно рассмотреть эти два.
Рисунок 20. Замените OverlayContainer пользовательским ElementOverlayContainer и предоставьте новый Overlay.
Теперь перейдите к предварительному просмотру веб-сайта, и DOM всплывающего слоя будет успешно прикреплен к элементу предварительного просмотра компонента.
Рисунок 21. Контейнер Overlay из cdk прикреплен к указанному dom. Частичный предварительный просмотр темы выполнен успешно.
В библиотеке компонентов DevUI также есть собственный OverlayContainerRef для некоторых компонентов и модальных ящиков, которые также необходимо соответствующим образом заменить. Наконец, всплывающие слои и другие всплывающие слои могут быть реализованы для идеальной поддержки локальных тем.
Краткое изложение знаний : Хороший шаблон абстракции может сделать модули заменяемыми и обеспечить элегантное аспектное программирование.
последнего случая, я бы хотел поговорить о менее формальном подходе. облегчить всем понимание природы провайдера, настройка провайдера по сути означает, что он помогает вам создавать экземпляры или сопоставлять их с существующим экземпляром.
Мы знаем, что при использовании cdkOverlay, если мы хотим, чтобы всплывающее окно следовало за полосой прокрутки и зависало в правильном положении, нам нужно добавить инструкцию cdkScrollable к полосе прокрутки.
Это все тот же сценарий, что и в предыдущем примере. Вся наша страница загружается через маршрутизацию. Для простоты я написал полосу прокрутки на хосте компонента.
Рисунок 22. Полоса прокрутки переполнения содержимого записывает overflow:auto в компоненте:host.
Таким образом, мы сталкиваемся с более сложной проблемой, которая определяется определением маршрутизатора, то есть <app-theme-picker-customize></app-theme-picker-customize>
, тогда как добавить инструкцию cdkScrollable? Решение заключается в следующем. Часть кода здесь скрыта, и остался только основной код.
Рис. 23. Создание экземпляра посредством внедрения и вызов жизненного цикла вручную.
Здесь экземпляр cdkScrollable создается посредством внедрения, а жизненный цикл вызывается синхронно на этапе жизненного цикла компонента.
Это решение не является формальным методом, но оно решает проблему. Оно оставлено здесь в качестве идеи и исследования на вкус.
Краткое изложение знаний : Поставщик конфигурации внедрения зависимостей может создавать экземпляры, но обратите внимание, что экземпляры будут рассматриваться как обычные классы обслуживания и не могут иметь полный жизненный цикл.
обратитесь к этому сообщению в блоге: «Рендеринг приложений Angular в терминале».
Рис. 24. Замените средство рендеринга RendererFactory2 и другое содержимое, чтобы разрешить запуск Angular на терминале.
Автор заменил RendererFactory2 и другие средства рендеринга, чтобы приложение Angular могло работать на терминале. В этом заключается гибкость дизайна Angular. Даже платформу можно заменить. Это мощно и гибко. Подробные сведения о замене можно найти в исходной статье и не будут здесь подробно описываться.
Краткое изложение знаний : Сила внедрения зависимостей заключается в том, что поставщик может настроить его самостоятельно и, наконец, реализовать логику замены.
В этой статье был представлен режим инверсии управления внедрением зависимостей и его преимущества. Также было описано, как внедрение зависимостей в Angular ищет зависимости, как настраивать провайдеров, как использовать ограниченные и фильтрующие декораторы для получения желаемого экземпляра и далее через N. Кейсы анализируют, как объединить знания по внедрению зависимостей для решения проблем, возникающих при разработке и программировании.
Правильно понимая процесс поиска зависимостей, мы можем настроить провайдера в точном местоположении (Случай 1 и 2), заменить другие экземпляры синглтонами (Случай 4 и 5) и даже подключиться, преодолевая ограничения вложенных пакетов компонентов. Случай 3) или используйте предоставленную кривую метода для реализации создания экземпляра инструкции (Случай 6).
Случай 5 кажется простой заменой, но для того, чтобы написать структуру кода, которую можно заменить, требуется глубокое понимание режима внедрения и более качественная и разумная абстракция каждой функции. Если абстракция не подходит, зависимости. нельзя использовать. Максимальный эффект от инъекции. Режим внедрения обеспечивает больше места для подключаемых, подключаемых и составных модулей, уменьшая связанность и повышая гибкость, благодаря чему модули могут работать вместе более элегантно и гармонично.
Мощная функция внедрения зависимостей может не только оптимизировать пути взаимодействия компонентов, но, что более важно, она также может обеспечить инверсию управления, открывая инкапсулированные компоненты для большего количества аспектов программирования, а реализация некоторой специфичной для бизнеса логики также может стать более гибкой.