Эта статья — перевод оригинальной статьи Luis Aviles "How to integrate Web Components using Lit in Angular"
Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.
Вступление
В этом руководстве я объясню необходимые шаги для интеграции веб-компонентов в Angular. Спойлер: Вы можете найти исходный код демонстрационного проекта в конце.
![](https://habrastorage.org/getpro/habr/upload_files/41c/e2f/9fd/41ce2f9fd2ad1d9538667095ea69d8d1.png)
Настройка проекта
В вашей локальной среде должны быть установлены следующие инструменты:
Node.js. Желательно последняя версия LTS.
Менеджер пакетов. Вы можете использовать npm или yarn. В этом руководстве будет использоваться npm.
Создание проекта Angular
Давайте создадим проект с нуля с помощью Angular CLI.
ng new angular-lit-web-components --routing --prefix corp --style css --skip-tests
Эта команда инициализирует базовый проект с использованием некоторых параметров конфигурации:
--routing
. Будет создан модуль маршрутизации.--prefix corp
. Он определяет префикс, который будет применяться к селекторам для созданных компонентов (в данном случаеcorp
). Значение по умолчанию -app
.--style css
. Расширение файла стилей.--skip-tests
. Это позволяет избежать генерации файлов.spec.ts
, которые используются для тестирования.
Установка Lit
Lit - это простая библиотека для создания быстрых и легких веб-компонентов.
Lit доступен через npm, давайте установим его как новую зависимость для текущего проекта.
npm install --save lit
Дополнительную информацию о Lit можно найти здесь.
Установка Web Components Polyfills
Есть несколько способов установить полифилы веб-компонентов. В этом случае мы установим его с помощью npm.
npm install --save @webcomponents/webcomponentsjs
Затем вы можете использовать webcomponents-loader.js
, который позволяет загружать минимальный пакет полифиллов.
Обновляем Angular конфиг
Вы можете загрузить полифиллы с помощью тега <script> в файл index.html. Однако в Angular это можно сделать путем добавления новых конфигураций ресурсов и скриптов в файл angular.json следующим образом:
"architect": {
"build": {
...
"options": {
...
"assets": [
"src/favicon.ico",
"src/assets",
{
"glob": "{*loader.js,bundles/*.js}",
"input": "node_modules/@webcomponents/webcomponentsjs",
"output": "node_modules/@webcomponents/webcomponentsjs"
}
],
"scripts": [
"node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"
]
...
},
"configurations": {
...
},
...
},
}
И входные, и выходные свойства должны относиться к корневому пути проекта. То же самое и с импортированным скриптом.
Создание веб-компонентов с использованием Lit
Настройка проекта завершена, и теперь пора создать наш первый веб-компонент с использованием Lit.
Давайте создадим папку src/web-components/card-user
, а затем создадим два файла: card-user.ts
и user.ts
.
Файл user.ts
будет определять общую модель, используемую реализацией веб-компонента и проектом Angular:
// user.ts
export interface User {
id: number;
fullName: string;
role: string;
avatar?: string;
}
Затем давайте напишем код для нашего веб-компонента в файле card-user.ts
:
// card-user.ts
import { LitElement, html, css } from 'lit';
import { property, customElement } from 'lit/decorators.js';
import { User } from './user';
@customElement('card-user')
export class CardUser extends LitElement {
static styles = css`
:host {
display: block;
}
.card {
box-shadow: 0 10px 10px 0 rgba(0, 0, 0, 0.5);
max-width: 160px;
}
.card-content {
padding: 10px;
}
`;
@property({ type: Object }) user?: User = {
id: 0,
fullName: 'Luis Aviles',
role: 'Software Engineer',
};
render() {
if (this.user === undefined) {
return '';
}
return html`
<div class="card">
<img
width="160px"
src=${this.user.avatar
? this.user.avatar
: 'assets/images/avatar.png'}
/>
<div class="card-content">
<h4>${this.user.fullName}</h4>
<p>${this.user.role}</p>
<button @click=${this.handleEdit}>Edit</button>
</div>
</div>
`;
}
private handleEdit() {
this.dispatchEvent(
new CustomEvent<User>('edit', {
detail: this.user,
})
);
}
}
Класс CardUser
определяет кастомный элемент как новый виджет для нашего проекта.
Декоратор
@customElement
позволяет определять компонент, используя для него имя:card-user
.Поле
static styles
определяет стили для компонента с литерала шаблонаcss
.Декоратор
@property
, который позволяет объявлять свойства кастомного элемента в удобном виде.Метод рендеринга возвращает содержимое HTML через литерал шаблона
html
. Эта функция будет вызываться каждый раз при изменении свойства пользователя.
Вы можете узнать больше о том, как создать компонент с помощью Lit здесь.
Использование веб-компонентов в Angular
Импортируем веб-компонент
Перед использованием любого веб-компонента в Angular, нам необходимо импортировать его следующим образом:
// app.component.ts
import { Component } from '@angular/core';
import '../web-components/card-user/card-user'; // <-- import the web component
@Component({
selector: 'corp-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {}
Используем кастомный элемент
Следующим шагом будет использование нового кастомного элемента <card-user>
как части шаблона. В этом случае давайте использовать его в нашем файле app.component.html
:
<card-user></card-user>
После сохранения этих изменений вы можете обнаружить ошибку в консоле (место, где вы запускаете ng serve).
Error: src/app/app.component.html:1:1 - error NG8001: 'card-user' is not a known element:
1. If 'card-user' is an Angular component, then verify that it is part of this module.
2. If 'card-user' is a Web Component, add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
1 <card-user></card-user>
~~~~~~~~~~~
src/app/app.component.ts:7:16
7 templateUrl: './app.component.html',
~~~~~~~~~~~~~~~~~~~~~~
Error occurs in the template of component AppComponent.
К счастью, предыдущее сообщение об ошибке достаточно понятно нам говорит, что нам нужно изменить файл app.module.ts
.
// app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule],
providers: [],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
Важная часть здесь - импортировать CUSTOM_ELEMENTS_SCHEMA
из пакета @angular/core
, а затем использовать его в свойстве schemas
. Вы можете найти больше информации об этом здесь.
Сохраните изменения еще раз, и волшебство произойдет :-)
![](https://habrastorage.org/getpro/habr/upload_files/fc3/48c/06e/fc348c06e0e439c107f322b2bbeb1d82.png)
Использование Property Binding
Удобно знать, что мы можем не только импортировать внешние компоненты в наше приложение, но и использовать привязку свойств.
Для этого определим свойство user
в файле app.component.ts
:
// app.component.ts
export class AppComponent {
user: User = {
id: 2,
fullName: 'Luis',
role: 'Software Engineer',
avatar: 'https://luixaviles.com/images/avatar@2x.png',
};
}
Затем мы можем обновить связанный шаблон и использовать квадратные скобки для привязки свойства:
<card-user [user]="user"></card-user>
Более того, вы можете создать массив пользователей, чтобы иметь возможность отображать их, используя другие полезные функции из нашего любимого фреймворка, например, структурная директива *ngFor
.
<card-user *ngFor="let user of users" [user]="user"></card-user>
Использование Event Binding
Пришло время слушать действия пользователя и реагировать на них через привязку событий в Angular. Как вы видели ранее, компонент corp-user
может отправлять событие edit
после нажатия кнопки «Изменить».
На этот раз воспользуемся круглыми скобками для привязки метода:
<card-user *ngFor="let user of users" [user]="user" (edit)="edit($event)"></card-user>
Наконец, давайте создадим метод редактирования в нашем компоненте.
// app.component.ts
//...
export class AppComponent {
//...
edit(event: Event) {
const user = (event as CustomEvent<User>).detail;
console.log('Edit user', user);
}
}
Метод edit
получает обобщенный Event
. Хотя кастомный элемент отправил объект User
через CustomEvent
. Затем мы можем использовать синтаксический оператор as
из TypeScript и получить к нему доступ с помощью свойства detail. На следующем снимке экрана показаны результаты в консоли браузера.
![](https://habrastorage.org/getpro/habr/upload_files/a8a/757/a75/a8a757a75b1e9e8c232046b79c478a0d.png)
Исходный код проекта
Вы можете найти полный код в этом репозитории GitHub: angular-lit-web-components. Не забудьте поставить звездочку ⭐️ и поэкспериментировать с кодом.