DevGang
Авторизоваться

Angular + веб-компоненты: полное руководство 

В этой статье вы узнаете, как включить один компонент Angular внутри страницы с помощью Angular elements. Мы подробно рассмотрим, как это работает, и как отлаживать приложение с помощью веб-компонентов.

В прошлом году мне было поручено включить несколько компонентов Angular в существующее приложение ASP.Net. Уже было реализовано беспорядочное решение (гигантский скрипт, который загружал классическое приложение Angular со всеми компонентами, независимо от их соответствия текущей странице), которое, очевидно, было «немного» неэффективным с точки зрения скорости. И, конечно же, этот подход был адом с точки зрения процесса разработки программного обеспечения, который требовал, чтобы я запускал сценарий Gulp, перестраивал приложение ASP.Net и жестко перезагружал страницу каждый раз, когда я менял крошечный бит CSS. Команда не была довольна ни производительностью приложения, ни процессом разработки, поэтому моей целью было

  1. Создайте новое решение, которое позволит включить один компонент Angular внутри страницы, а не все приложение.
  2. Работал бы быстрее и не загружал огромные ненужные скрипты.
  3. Это позволит ускорить сборку разработчика без необходимости постоянного повторного запуска скрипта.

Поэтому, естественно, я обратился к веб-компонентам.

Но что такое веб-компоненты?

Веб-компоненты - это концепция компонентов из таких фреймворков, как React или Angular, но построенных для Интернета в целом, независимо от фреймворка. Что это значит?

Мы привыкли к нашим простым тегам HTML в нашем пользовательском интерфейсе. Например, мы знаем, такие теги как div, span и другие. Они имеют предопределенное поведение и могут использоваться в разных местах нашего проекта.

Веб-компоненты по существу позволяют нам создавать новые теги / элементы HTML с помощью JavaScript. Давайте посмотрим на небольшой пример того, как это делается исключительно с помощью JavaScript.

class SpanWithText extends HTMLSpanElement {
    constructor() {
        super();
        this.innerText = `Hello, I am a Web Component!`;
    }
}
customElements.define('span-with-text', SpanWithText);

Здесь мы создаем класс (точно так же, как мы пишем класс для компонентов в Angular), который расширяется от HTMLElement (в нашем случае HTMLSpanElement, если быть более точным), и всякий раз, когда этот элемент создается, он будет выполнять innerText со значением Hello, I am a Web Component. Итак, всякий раз, когда мы используем наш элемент в DOM, внутри него уже будет заполненный текст.

<span-with-text></span-with-text>

Круто, могу ли я использовать его с Angular?

Конечно, было бы здорово иметь возможность превратить наши компоненты Angular в веб-компоненты и использовать их где угодно, независимо от среды. Оказывается, с помощью @ angular/elements мы можем сделать именно это.

Как это работает:

Перво-наперво, мы собираемся запустить новое приложение Angular. Мы собираемся создать его со специальным флагом, чтобы он создавал только файлы конфигурации, а не шаблонный код (AppModule / AppComponent и т.д.).

ng new web-components --createApplication=false

Теперь давайте создадим наш первый веб-компонент; внутри нашего нового каталога проекта:

ng generate application FirstWebComponent  --skipInstall=true

Добавьте этот флаг, чтобы пропустить переустановку любых зависимостей.

Итак, эта команда создает папку projects внутри нашего корневого каталога, и в ней будет папка с именем FirstWebComponent. Если мы заглянем внутрь, мы увидим типичное приложение Angular: файлы main.ts, app.module, app.component и другие. Теперь нам нужно получить библиотеку @angular/elements, поэтому мы запускаем:

ng add @angular/elements

Это перенесет библиотеку в нашу папку node_modules, чтобы мы могли использовать ее для превращения наших компонентов в веб-компоненты.

Важно понимать, что компоненты Angular превращены в веб-компоненты и являются простыми компонентами Angular - ничто в них не должно отличаться для использования в качестве веб-компонентов. Вы можете сделать что - либо внутри них, что вы бы делали с обычным Angular компонентом, и он будет работать правильно. Любые шаги по настройке, которые необходимо выполнить, чтобы заставить ваши компоненты работать, выполняются на уровне модуля; в самих компонентах ничего не меняется. Это означает, что вы можете без особых хлопот превратить практически любой существующий компонент Angular в веб-компонент.

На следующем этапе давайте создадим компонент Angular, который станет нашим веб-компонентом; внутри нашего нового проекта:

ng generate component UIButton

Теперь нам нужно заставить Angular понять, что мы не хотим, чтобы он рассматривал этот компонент как обычный компонент Angular, а скорее как нечто иное. Это делается на уровне начальной загрузки модуля - что нам нужно сделать, так это реализовать метод ngDoBootstrap в нашем AppModule и сообщить нашему модулю, чтобы он определил настраиваемый элемент. Для этого воспользуемся функцией createCustomElement из пакета @angular/elements.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, DoBootstrap, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';

@NgModule({
  declarations: [
    UIButtonComponent,
  ],
  imports: [
    BrowserModule,
  ],
  entryComponents: [UIButtonComponent],
})
export class AppModule implements DoBootstrap {

  constructor(private injector: Injector) {
    const webComponent = createCustomElement(UIButtonComponent, {injector});
    customElements.define('ui-button', webComponent);
  }

  ngDoBootstrap() {}
 }

Обратите внимание на несколько важных моментов:

  1. Мы должны передать в injector наш веб-компонент вручную. Это необходимо для обеспечения работы внедрения зависимостей во время выполнения.
  2. Мы должны поместить наш компонент в массив entryComponents. Это необходимо для начальной загрузки веб-компонента.
  3. createCustomElement - это функция, которая фактически превращает наш собственный компонент в веб-компонент - мы передаем результат этой функции customElements.define, а не нашему компоненту Angular.
  4. Селектор нашего компонента Angular не имеет значения. То, как он будет вызываться из другого шаблона HTML, определяется строкой, которую мы передаем функции customElements.define. В этом случае он будет называться <ui-button></ui-button>.
  5. Селектор, который мы передаем функции customElements.define, должен состоять из двух или более слов, разделенных тире. Это скорее требование API пользовательских элементов (чтобы он мог отличать пользовательские элементы от собственных HTML-тегов), чем причуда самого Angular.

Наш следующий шаг - создание приложения!

ng build FirstWebComponent

Заглянув внутрь папки dist, мы увидим следующее:

Это файлы, которые нам нужно включить в другое приложение, чтобы иметь возможность использовать наш веб-компонент. Для этого мы можем скопировать эти файлы в другой проект и включить его разными способами:

1. Если мы собираемся использовать его в приложении React, мы можем установить полифилл, а затем включить наши файлы с помощью простых операторов import.

2. Чтобы использовать его в простом HTML-приложении, мы должны включить все наши сгенерированные файлы через теги скрипта, а затем использовать компонент:

<html>
  <head>
    <script src="./built-files/polyfills.js"></script>
    <script src="./built-files/vendor.js"></script>
    <script src="./built-files/runtime.js"></script>
    <script src="./built-files/styles.js"></script>
    <script src="./built-files/scripts.js"></script>
  </head>
  <body>
    <ui-button></ui-button>
  </body>
</html>

3. Чтобы использовать его в другом приложении Angular, нам действительно не нужно создавать компонент; поскольку это простой компонент Angular, мы можем просто импортировать и использовать его. Но если у нас есть доступ только к скомпилированным файлам, мы можем включить их в поле scripts в нашем файле angular.json в целевой проект и добавить schemas: [CUSTOM_ELEMENTS_SCHEMA], в наш файл app.module.ts.

Итак, первая часть была легкой. Несколько замечаний:

1. Если у нас есть @Input внутри нашего веб-компонента, шаблон именования изменяется, когда мы его используем. Мы используем camelCase в нашем компоненте Angular, но для доступа к этому вводу из другого файла HTML нам нужно будет использовать kebab-case:

@Component({/* metadata */})
export class UIButtonComponent {
  @Input() shouldCountClicks = false;
}
<ui-button should-count-clicks="true"></ui-button>

2. Если у нас есть @Output внутри нашего компонента Angular, мы можем прослушивать генерируемые синтетические события через addEventListener. Другого пути нет, в том числе и React - обычный on<EventName={callback}> не сработает, так как это синтетическое мероприятие.

Но как насчет совместимости браузера?

Пока все хорошо - мы создали наше приложение и успешно протестировали его в крупном современном браузере, например Chrome. Но как насчет проклятия всех веб-разработчиков - IE? Если мы откроем наше приложение, в котором мы используем веб-компонент, мы увидим, что наша кнопка со счетчиком успешно отрисована. Итак, мы в порядке? Не совсем.

Если бы у нас была функция, которая заставляет компонент подсчитывать клики по кнопке, то после того, как мы щелкнем в Internet Explorer, мы увидим, что счетчик не обновляется. Чтобы избавить вас от дальнейших исследований, проблема в том, что обнаружение изменений в веб-компонентах Angular не работает в IE. Итак, должны ли мы забыть о веб-компонентах в IE? Нет, к счастью, есть простой обходной путь. Мы должны либо реализовать новую стратегию зоны обнаружения изменений, либо импортировать ее. Есть небольшой пакет с тем, что мы хотим:

npm i elements-zone-strategy

И немного изменим наш файл app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, DoBootstrap, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';

@NgModule({
  declarations: [
    UIButtonComponent,
  ],
  imports: [
    BrowserModule,
  ],
  entryComponents: [UIButtonComponent],
})
export class AppModule implements DoBootstrap {

  constructor(private injector: Injector) {
    const strategyFactory = new ElementZoneStrategyFactory(UIButtonComponent, injector);
    const webComponent = createCustomElement(UIButtonComponent, {injector, strategyFactory});
    customElements.define('log-activity', webComponent);
  }

  ngDoBootstrap() {}
 }

Здесь мы просто вызвали конструктор ElementZoneStrategyFactory, получили новый strategyFactory и передали его функции createCustomElement вместе с injector.

Теперь вы можете открыть тот же компонент в IE и - удивительно - он работает! Обнаружение изменений теперь работает в IE, как и ожидалось.

Не забываем про полифиллы

Вы будете регулярно посещать этот файл polyfills.ts. Мой совет - настраивайте и не загружайте сразу все полифиллы, а только те, которые необходимы.

Как мне отлаживать?

Отладка (и сообщения об ошибках в целом) - это небольшая проблема в веб-компонентах Angular. У вас нет горячей перезагрузки, и вы запускаете свои встроенные компоненты в другой среде, а не в приложении Angular, поэтому вы можете задаться вопросом, как вы могли бы их получить. Фактически, вы можете немного изменить файл index.html и поставить селектор своего веб-компонента вместо app-root:

Затем запустите приложение как обычно:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>UIButton</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <ui-button></ui-button>
</body>
</html>
ng serve UIButton

Это создаст приложение Angular, которое содержит только ваш веб-компонент. Положительным моментом является то, что у вас будет горячая перезагрузка (это большое дело). Но есть и обратная сторона: сообщения об ошибках часто перевариваются: например, если вы забыли предоставить один из своих сервисов, в обычном приложении Angular вы получите ошибку с StaticInjectorError подробным описанием того, какой сервис не была предоставлен. В нашем случае нам просто будет представлен пустой экран и никаких ошибок - загадка, которую нужно исследовать и из-за которой можно расстраиваться.

Вывод

Эта базовая настройка дает нам все необходимое для создания и развертывания приложений, использующих компоненты Angular и веб-компоненты. Но эта установка далека от идеала - нам бы хотелось иметь такие скрипты как:

  1. Автоматическое создание новых веб-компонент
  2. Скрипты для автоматического запуска нашего процесса сборки
  3. Горячая перезагрузка

Источник:

#Angular
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

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

Попробовать

Оплатив хостинг 25$ в подарок вы получите 100$ на счет

Получить