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

Текущая документация о том, как начать работу с элементами, немного скудна, а документация по angular не очень помогает вам с переносом ваших пользовательских элементов в другие фреймворки. Я также оказался в ситуации, когда я хотел создать компонент, который можно было бы использовать как в приложении angular, так и в ванильном javascript. Было бы здорово, если бы я мог просто импортировать компонент, как если бы это был любой другой компонент библиотеки angular для приложения angular, но как веб-компонент для всего остального. К счастью, это было несложно.

Давайте начнем

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

Во-первых, давайте начнем с нового проекта

ng new ElementsExample

Также нам нужно добавить пакет elements

ng add @angular/elements

Затем давайте создадим пользовательский компонент, но давайте превратим его в библиотеку.

ng generate @schematics/angular:library --name=click-tracker

Преимущество построения его как библиотеки заключается в том, что он может использоваться несколькими приложениями angular, и каждое из этих приложений получает возможность использовать общедоступный API компонента. Таким образом, вы получаете автозавершение кода и поддержку IDE, что и для любого стороннего компонента angular. Давайте добавим компонент для отслеживания кликов в новую библиотеку.

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
@Component({
  selector: 'ct-click-tracker',
  template: `
    <button (click)="increment()">{{ clicks }} clicks</button>
  `,
  styles: [`
    button {
      background-color: cornflowerblue;
      padding: 10px;
      border: none;
      border-radius: 5px;
    }
  `]
})
export class ClickTrackerComponent implements OnInit {
  @Input('step') step: number = 1;
  @Output() increase: EventEmitter<any> = new EventEmitter<any>();
  public clicks: number = 0;
  constructor() { }
  ngOnInit(): void { }
  increment() {
    this.clicks = this.clicks + (+this.step);
    this.increase.emit({numClicks: this.clicks});
  }
}

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

ng build click-tracker --tsConfig=projects/click-tracker/tsconfig.lib.prod.json

Кроме того, мы создаем библиотеку для производства, потому что вы, вероятно, получите предупреждение в vscode, потому что языковая служба angular ищет файл метаданных, который создается только при сборке для производства.

Это здорово, но пока мы можем использовать это только с приложениями angular.

Приложение Custom Element

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

ng generate @schematics/angular:application --name=click-tracker-element

А теперь обновим модуль

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { ClickTrackerModule, ClickTrackerComponent } from 'click-tracker';
@NgModule({
  declarations: [],
  imports: [
    BrowserModule,
    ClickTrackerModule
  ],
  providers: [],
  bootstrap: []
})
export class AppModule {
  constructor(private injector: Injector) { }
  ngDoBootstrap() {
    const ClickTrackerElm = createCustomElement(ClickTrackerComponent, {injector: this.injector});
    customElements.define('ct-click-tracker', ClickTrackerElm);
  }
}

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

Теперь, если мы создадим все, библиотеку и приложение для простых элементов, мы получим результат в каталоге dist. В выводе приложения простого элемента мы видим файлы js, которые обычно импортируются в index.html. Мы можем объединить их вместе, чтобы создать пакет, который затем может быть импортирован другими веб-приложениями.

ng build click-tracker-element --aot --buildOptimizer --extractCss --extractLicenses --no-namedChunks --optimization --no-sourceMap --no-vendorChunk
cat dist/click-tracker-element/runtime-es5.js dist/click-tracker-element/polyfills-es5.js dist/click-tracker-element/main-es5.js > dist/ct-element.js

Использование настраиваемого элемента повсюду

Наконец, мы можем использовать этот связанный файл для добавления настраиваемого элемента с помощью простого ole vanilla javascript.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Vanilla JS ClickTracker App</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>
  <ct-click-tracker step="5"></ct-click-tracker>
  <script src="ct-element.js"></script>
  <script>
    const ctElm = document.querySelector('ct-click-tracker');
    ctElm.addEventListener('increase', (event) => {
      console.log(event.detail);
    });
  </script>
</body>
</html>

Как видите, все работает как собственный веб-компонент, включая входы и выходы событий. У вас все еще есть компонент библиотеки, который будет использоваться в приложении angular, и теперь у вас есть пакет, который можно использовать везде, где используются javascript и html.

Масштабирование вашей библиотеки

Говоря об этом, я столкнулся с другими проблемами, связанными с масштабированием этого типа разработки. Для тех, кто поддерживает библиотеку angular, это может не вызывать беспокойства, но для тех, кто этого не сделал, у вас есть несколько вариантов. Во-первых, вы можете превратить библиотеку в полный список библиотечных компонентов для angular, а затем создать простое приложение, которое объединяет их все. При этом вы получите дрожание дерева в своих приложениях angular, но вашим приложениям, не относящимся к angular, придется импортировать полную библиотеку компонентов. Вы можете настроить приложение-оболочку в зависимости от ваших потребностей, но это приведет к нескольким дублированным усилиям. Кроме того, к сожалению или нет, большой импорт всех компонентов - это как бы то, как это делалось уже давно (подумайте о jQuery и других библиотеках js).

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

Однако следует учитывать тот факт, что вам нужно обернуть компонент библиотеки в приложение angular, и этот код будет добавлен в ваш пакет. Если вы упаковываете каждый элемент отдельно, вы получите код времени выполнения angular в каждом из ваших пакетов элементов, что увеличит общий размер импортированного кода. Использование библиотеки с одним элементом может устранить это, но в конечном итоге компромисс будет зависеть от вашего варианта использования и количества элементов, которые вы планируете создавать.

Вывод

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

Вот последнее репо для этого примера: https://github.com/smarth55/angular-elements-example.

Если у вас есть какие-либо вопросы или предложения, или даже у вас есть собственный способ поделиться элементами angular, дайте мне знать, оставив комментарий!