От автора: Angular – фреймворк, любимый многими в сообществе JS. Angular предоставляет библиотеку для создания инкапсулированных компонентов, вставку зависимостей, создание языковых шаблонов с назначением данных, роутер приложения, построенный на observables, а также интерфейс командной строки с низким порогом входа. Этот фреймворк не такой гибкий, как другие, однако упрямый характер Angular позволяет большим командам кодить по существующим стандартам, а не разрабатывать свои. Также Angular упрощает разделение логики отображения (компоненты) от бизнес логики (сервисы и логика), поэтому несколько команд может работать над разными частям приложения.
Веб-компоненты
Веб компоненты – набор стандартных API, позволяющих нативно создавать кастомные HTML теги со своей функциональностью и жизненным циклом компонентов. API включают в себя спецификацию по кастомным элементам, теневой DOM, который позволяет изолировать внутренности компонента, а также импорты HTML, где описывается, как необходимо загружать компоненты, и как их использовать в веб-приложении. Основная задача веб-компонентов – инкапсуляция кода компонентов в хорошие, повторно используемые пакеты для достижения максимальной совместимости.
Angular
Angular 2+ делает много тяжелой работы за вас, компилируя шаблон компонента в рендерер JS и синхронизируя данные между ним и объектом компонента. Делается это через однонаправленный поток данных и менеджер жизненных циклов, который определяет изменения в свойствах компонента и указывает, какой шаблон необходимо заново отрендерить. Язык шаблонов Angular – это в основном HTML с синтаксическим сахаром, поэтому компилятор уже знает, как создавать узлы DOM по тегам в шаблоне. По умолчанию компилятор понимает только стандартные теги HTML и компоненты Angular, зарегистрированные в приложении. Как только компилятор Angular узнает о веб-компонентах, в приложении сразу можно использовать любой веб-компонент, зарегистрированный в браузере, как родной HTML элемент.
Начнем с генерации проекта с помощью angular-cli (полный код) и будем использовать те же веб-компоненты, что и в уроке по веб-компонентам в React. Начнем.
Активация веб-компонентов
Во-первых, давайте активируем веб-компоненты в нашем проекте в src/app/app.module.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import { BrowserModule } from '@angular/platform-browser'; import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { AppComponent } from './app.component'; import '../web-components/Tabs'; import '../web-components/Tab'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule ], providers: [], bootstrap: [AppComponent], schemas: [ CUSTOM_ELEMENTS_SCHEMA ] }) export class AppModule { } |
Сначала мы импортировали CUSTOM_ELEMENTS_SCHEMA из @angular/core и добавили ее в объявление @NgModule приложения в свойстве schemas. Так мы разрешаем компилятору шаблонов Angular веб-компоненты и их атрибуты. Также мы импортировали наши веб-компоненты, и они зарегистрировались браузером. Так как веб-компоненты – это JS файлы, то нам нужно сказать TypeScript, чтобы тот разрешил импорт JS файлов в tsconfig.json:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
{ "compileOnSave": false, "compilerOptions": { "outDir": "./dist/out-tsc", "sourceMap": true, "declaration": false, "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "target": "es5", "allowJs": true, "typeRoots": [ "node_modules/@types" ], "lib": [ "es2017", "dom" ] } } |
Мы добавили свойство compilerOptions.allowJs и установили ему значение true. Теперь мы можем использовать веб-компоненты в приложении Angular и компонентах.
Использование компонентов в шаблоне
Теперь когда компилятор Angular знает, что нужно разрешить наши кастомные теги, мы можем использовать веб-компоненты в любом шаблоне проекта. Начнем с добавления очень простого компонента статичных вкладок в src/app/app.component.html:
1 2 3 4 5 6 7 8 9 10 11 12 |
<x-tabs> <x-tab title="Tab 01" closable="true"> <div> <h3>Tab 01 Content</h3> </div> </x-tab> <x-tab title="Tab 02"> <div> <h3>Tab 02 Content</h3> </div> </x-tab> </x-tabs> |
Этот простой компонент вкладок использует первую версию рекомендаций веб-компонентов. Компонент x-tabs создаст маркированный список с заголовками x-tab, которые будут отвечать за каждую вкладку. Как видите, в разметке не сказано, где и как ul будет отрисован, что делает его хорошим компонентом, так как часть компонента генерируется динамически. В компоненте есть события, которые будут запущены, динамически генерируя разметку внутри компонента и модифицируя структуру DOM.
Обработка событий и коллекций
Когда закрывается вкладка, которую можно закрыть, компонент x-tabs запускает событие tabclosed. Обработка событий веб-компонентов следует тому же подходу, который используется в событиях компонентов Angular. Мы изменим наш шаблон (src/app/app.component.html), чтобы прикрепить событие:
1 2 3 4 5 6 7 8 9 10 11 12 |
<x-tabs (tabclosed)="onTabClosed($event)"> <x-tab title="Tab 01" closable="true"> <div> <h3>Tab 01 Content</h3> </div> </x-tab> <x-tab title="Tab 02"> <div> <h3>Tab 02 Content</h3> </div> </x-tab> </x-tabs> |
И добавим метод onTabClosed в наш компонент (src/app/app.component.ts):
1 2 3 |
onTabClosed({ detail: component }) { console.log(component); } |
При запуске приложения клик по кнопке закрытия первой вкладки запускает наш обработчик события и логирует узел x-tab, который был удален.
Так как Angular видит веб-компоненты как компоненты первого уровня, мы можем с помощью декоратора @ViewChildren собрать вкладки с шаблона. Нам нужно изменить шаблон и добавить в узлы x-tab идентификаторы:
1 2 3 4 5 6 7 8 9 10 11 12 |
<x-tabs (tabclosed)="onTabClosed($event)"> <x-tab title="Tab 01" #tab closable="true"> <div> <h3>Tab 01 Content</h3> </div> </x-tab> <x-tab #tab title="Tab 02"> <div> <h3>Tab 02 Content</h3> </div> </x-tab> </x-tabs> |
Добавим свойство с декоратором @ViewChildren в наш компонент:
1 |
@ViewChildren('tab') tabs: QueryList<ElementRef>; |
Теперь если открыть приложение в Augury, то можно заметить, что свойство tabs наполнено объектами ElementRef, в которых хранятся ссылки на компоненты x-tab:
Встает одна проблема: при закрытии первой вкладки не обновляется коллекция tabs. Так происходит потому, что шаблон приложения не обновляется, когда изменяется нижележащий DOM (как наш компонент x-tabs). Если запустить повторный рендеринг компонента, это откроет закрытую вкладку.
Управление вкладками
В простом приложении без закрываемых вкладок компонент x-tabs будет работать сразу же. Тем не менее, если нам нужны закрываемые вкладки, которые можно отслеживать в Angular, нам нужно управлять неким состоянием и позволить Angular отслеживать рендеринг компонентов x-tab. Сначала мы изменим наш шаблон приложения и добавим в него новый набор вкладок (для этого урока) и с помощью ngFor сгенерируем их из массива объектов (tabObjects).
1 2 3 4 5 6 7 8 |
<x-tabs (tabclosed)="onStatefulTabClosed($event)"> <x-tab *ngFor="let tab of tabObjects; let i = index" #statefulTab [attr.data-index]="i" [title]="tab.title" [closable]="tab.closable"> <div> <h3>{{ tab.pane.title }}</h3> </div> </x-tab> </x-tabs> |
Затем мы добавим в компонент массив tabObjects, коллекцию для хранения наших элементов statefulTab, а также обработчик события onStatefulTabClosed:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import { Component, ViewChildren, QueryList, ElementRef } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { tabObjects = [ { title: 'Tab 01', closable: true, pane: { title: 'Tab 01 Content' } }, { title: 'Tab 02', closable: false, pane: { title: 'Tab 02 Content' } } ]; @ViewChildren('tab') tabs: QueryList<ElementRef>; @ViewChildren('statefulTab') statefulTabs: QueryList<ElementRef>; onTabClosed({ detail: component }) { console.log(component); } onStatefulTabClosed(event) { event.preventDefault(); const component = event.detail; this.tabObjects.splice(component.dataset.index, 1); } } |
Так как мы генерируем компоненты x-tab из массива tabObjects, любые изменения в нем внутри обработчика события вызовут повторный рендеринг. Также нам нужно отключить стандартное поведение события tabclosed (удаление x-tab и li), так как Angular будет делать это автоматически. Если заглянуть в Angular после клика по кнопке закрытия первой вкладки в наших stateful вкладках, можно увидеть, что Angular обновляет коллекцию statefulTabs, чтобы она совпадала с текущим состоянием компонента:
Заключение
Работать с веб-компонентами в приложении Angular и компонентах так же легко, как использовать компоненты Angular. Синтаксис шаблона, назначение свойств и механизмы обработки событий уже знакомы разработчикам Angular и будут работать как со сторонними веб-компонентами, так и с компонентами Angular. В почти всех случаях можно обойтись без создания блока-обертки.
Веб-компоненты обеспечивают значительный потенциал взаимодействия компонентов между фреймворками. Будем надеяться, что все современные фреймворки упростят импорт веб-компонентов, как это сделал Angular, и позволят разработчикам приложений легко использовать компоненты в множестве разных современных контекстов.
Автор: Bryan Forbes
Источник: //www.sitepen.com/
Редакция: Команда webformyself.