Использование web-компонентов в Angular
Веб-компоненты позволяют нам создавать переиспользуемые, настраиваемые элементы. Основное преимущество веб-компонентов - это их интероперабельность: поскольку они поддерживаются нативно в браузерах, веб-компоненты могут использоваться в любой HTML-среде, с любым фреймворком или вообще без фреймворка.
Одним из основных аспектов веб-компонентов является инкапсуляция. Вы можете разделять и скрывать структуру разметки, стиль и поведение, чтобы различные части страницы не конфликтовали между собой.
Поэтому веб-компоненты идеально подходят для разработки дизайн-систем, компонентов, которые можно использовать повторно, а так же встраиваемых виджетов. Давайте посмотрим, как использовать нативный веб-компонент с лучшими библиотеками в отрасли:
Создание веб-компонентов с Lit
Создание веб-компонента с нуля на чистом JavaScript может быстро превратиться в легаси, который сложно масштабировать и поддерживать. К счастью, мы можем создавать быстрые, легкие веб-компоненты с помощью Lit
.
В основе Lit
лежит базовый класс компонентов, который убивает необходимость в шаблонах, предоставляя реактивное состояние, стили с областью видимости и декларативную систему шаблонов.
Хотя мы будем использовать Lit
, я рекомендую изучить, как разрабатывать веб-компоненты с использованием нативного JavaScript
.
Для начала реализуем простейший Select
, который будет работать через нативный API. Вопросы сборщиков здесь обсуждаться не будут, можно использовать любой.
import { css, html, LitElement } from 'lit'; export class SelectElement<T extends Record<string, unknown>> extends LitElement { static styles = css` button[active] { background-color: honeydew } ` @property({ type: Array }) data: T[] = []; @property({ attribute: 'id-key' }) idKey: keyof T = 'id' as keyof T; @property({ attribute: 'val-key' }) valKey: keyof T = 'label' as keyof T; @property({ state: true }) private activeItem: T | null = null; render() { return html`` } } customElements.define('ui-select', SelectElement);
Мы создаем SelectElement
и определяем три входных параметра — data
, idKey
и valKey
. Мы также определяем состояние activeItem
для отслеживания текущего выбранного элемента. Добавим шаблон:
import { css, html, LitElement } from 'lit'; import { repeat } from 'lit/directives/repeat.js'; export class SelectElement extends LitElement { ... selectItem(item: T) { this.activeItem = item; const event = new CustomEvent<T>('select-item', { detail: item, bubbles: true, composed: true }); this.dispatchEvent(event); } render() { return html` <p>Active: ${this.activeItem ? this.activeItem[this.valKey] : 'None' }</p> ${repeat( this.data, current => current[this.idKey], current => html` <button ?active=${this.activeItem?.[this.idKey] === current[this.idKey]} @click=${() => this.selectItem(current)}> ${current[this.valKey]} </button> `)} ` } } customElements.define('ui-select', SelectElement);
Мы используем директиву repeat
для эффективного рендеринга наших элементов. Она принимает три параметра - collection
, keyFunction
, которая принимает элемент в качестве аргумента и возвращает уникальный ключ для него, и itemTemplate
, которая принимает элемент и его текущий индекс в качестве аргументов и возвращает TemplateResult
.
При щелчке на элементе он помечается как активный и отправляется пользовательское событие, на которое родитель может подписаться.
Мы используем хорошую возможность из Lit
, называемую выражениями для атрибутов boolean
. Атрибут active
будет добавлен или удален в зависимости от результата выражения. Мы используем его для стилизации активного элемента.
Если вы ищете генератор lit-файлов, то может пригодиться эта библиотека.
Использование веб-компонентов в Angular
Сначала нужно использовать схему CUSTOM_ELEMENTS_SCHEMA
. Angular игнорирует пользовательские элементы (названные с дефисами), которые он не распознает, вместо того чтобы генерировать ошибку.
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { TodosComponent } from './todos.component'; @NgModule({ declarations: [TodosComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class TodosModule {}
Теперь можно использовать веб-компонент ui-select
внутри компонента TodosComponent
:
import '@org/ui/lib/select'; import { Component } from '@angular/core'; import { Todo, randTodo } from '@ngneat/falso'; @Component({ selector: 'app-todos', template: ` <button (click)="replace()">Replace</button> <ui-select [data]="todos" (select-item)="onSelect($event)" val-key="title"> </ui-select> ` }) export class TodosComponent { todos: Todo[] = randTodo({ length: 3 }); replace() { this.todos = randTodo({ length: 3 }); } onSelect(e: CustomEvent<Todo>) { console.log(e.detail); } }
Как показывает пример, интеграция Angular с веб-компонентами работает из коробки. Angular связывает свойство компонента todos
со свойством данных в элементе ui-select
. Привязка событий слушает событие select-item
и вызывает метод onSelectItem()
компонента всякий раз, когда оно срабатывает.
Это вольный перевод материала