Эта статья — перевод оригинальной статьи Luis Aviles "How to integrate Web Components using Lit in Angular"

Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.

Вступление

В этом руководстве я объясню необходимые шаги для интеграции веб-компонентов в Angular. Спойлер: Вы можете найти исходный код демонстрационного проекта в конце.

Настройка проекта

В вашей локальной среде должны быть установлены следующие инструменты:

  • 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. Вы можете найти больше информации об этом здесь.

Сохраните изменения еще раз, и волшебство произойдет :-)

Использование 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. На следующем снимке экрана показаны результаты в консоли браузера.

Исходный код проекта

Вы можете найти полный код в этом репозитории GitHub: angular-lit-web-components. Не забудьте поставить звездочку ⭐️ и поэкспериментировать с кодом.

Комментарии (0)