Создать Angular-компонент для использования в Creatio

Сложный
PDF

Для встраивания Angular-компонентов в приложение Creatio используется функциональность Angular Elements. Angular Elements — это npm-пакет, который позволяет упаковывать Angular-компоненты в Custom Elements и определять новые HTML-элементы со стандартным поведением (Custom Elements является частью стандарта Web-Components).

Создание пользовательского Angular-компонента 

1. Настроить окружение для разработки компонентов средствами Angular CLI 

Для этого установите:

  1. Node.js® и npm package manager.
  2. Angular CLI.

    Чтобы установить Angular CLI выполните в системной консоли команду:

    npm install -g @angular/cli
    
    npm install -g @angular/cli@8
    

2. Создать Angular приложение 

Выполните в консоли команду ng new и укажите имя приложения, например angular-element-test.

Создание Angular приложения
ng new angular-element-test --style=scss

3. Установить пакет Angular Elements 

Из папки приложения, созданного на предыдущем шаге, выполните в консоли команду.

Установка пакета Angular Elements
ng add @angular/elements

4. Создать компонент Angular

Чтобы создать компонент выполните в консоли команду.

Создание компонента Angular
ng g c angular-element

5. Зарегистрировать компонент как Custom Element 

Чтобы настроить трансформацию компонента в пользовательский HTML-элемент, необходимо внести изменения в файл app.module.ts:

  1. Добавьте импорт модуля createCustomElement.
  2. В модуле в секции entryComponents укажите имя компонента.
  3. В методе ngDoBootstrap зарегистрируйте компонент под HTML-тегом.
    app.module.ts
    import { BrowserModule } from "@angular/platform-browser";
    import { NgModule, DoBootstrap, Injector, ApplicationRef } from "@angular/core";
    import { createCustomElement } from "@angular/elements";
    import { AppComponent } from "./app.component";
    @NgModule({
        declarations: [AppComponent],
        imports: [BrowserModule],
        entryComponents: [AngularElementComponent]
    })
    export class AppModule implements DoBootstrap {
        constructor(private injector: Injector) {
        }
        ngDoBootstrap(appRef: ApplicationRef): void {
            const el = createCustomElement(AngularElementComponent, { injector: this._injector });
            customElements.define('angular-element-component', el);
        }
    }
    

6. Выполнить сборку приложения 

  1. При сборке проекта сгенерируются несколько *.js-файлов. Для простоты дальнейшего использования веб-компонента в Creatio, созданные после сборки файлы рекомендуется поставлять в одном файле. Для этого необходимо в корне приложения создать скрипт build.js.

    Пример build.js
    const fs = require('fs-extra');
    const concat = require('concat');
    const componentPath = './dist/angular-element-test/angular-element-component.js';
     
    (async function build() {
       const files = [
          './dist/angular-element-test/runtime.js',
          './dist/angular-element-test/polyfills.js',
          './dist/angular-element-test/main.js',
          './tools/lodash-fix.js',
       ].filter((x) => fs.pathExistsSync(x));
       await fs.ensureFile(componentPath);
       await concat(files, componentPath);
    })();

    Если в веб-компоненте используется библиотека lodash, то для ее работы в Creatio необходимо main.js (и при необходимости styles.js) объединять со скриптом, устраняющим конфликты по lodash. Для этого в корне Angular-проекта создаем папку tools и файл lodash-fix.js.

    lodash-fix.js
    window._.noConflict();

    Важно. Если Вы не используете библиотеку lodash, то файл lodash-fix.js создавать не нужно и строку './tools/lodash-fix.js' из масива files необходимо убрать.

    Дополнительно для выполнения скрипта в build.js необходимо установить в проекте пакеты concat и fs-extra как dev-dependency. Для этого выполните в командной строке команды:

    Установка дополнительных пакетов
    npm i concat -D
    npm i fs-extra -D

    По умолчанию для созданного приложения могут быть установлены настройки файла browserslist, которые создают сразу несколько сборок для браузеров, которые поддерживают ES2015, и для тех, которым нужен ES5. Для данного примера мы собираем Angular элемент для современных браузеров. 

    Пример browserslist
    # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
    # For additional information regarding the format and rule options, please see:
    # https://github.com/browserslist/browserslist#queries
     
    # You can see what browsers were selected by your queries by running:
    #   npx browserslist
     
    last 1 Chrome version
    last 1 Firefox version
    last 2 Edge major versions
    last 2 Safari major versions
    last 2 iOS major versions
    Firefox ESR
    not IE 11

    Важно. Если Вам необходимо поставлять веб-компонент в браузеры, которые не поддерживают ES2015, нужно либо править масив файлов в build.js, либо изменить target в tsconfig.json (target: "es5"). Внимательно проверяйте названия файлов после сборки в папке dist. Если они не совпадают с названиями в масиве build.js, их нужно изменить в файле.

  2. Добавьте в package.json команды, которые отвечают за сборку элемента. В результате их выполнения, вся бизнес логика помещается в один файл angular-element-component.js, с которым мы будем работать далее.

    package.json
    ....
    "build-ng-element": "ng build --output-hashing none && node build.js",
    "build-ng-element:prod": "ng build --prod --output-hashing none && node build.js",
    ...

    Важно. Рекомендуем при разработке, выполнять сборку приложения без параметра --prod.

Подключение Custom Element в Creatio 

Созданный в результате сборки файл angular-element-component.js необходимо встроить в пакет Creatio как файловый контент.

1. Разместить файл в статическом контенте пакета 

Для этого скопируйте файл в папку Название пользовательского пакета\Files\src\js, например, MyPackage\Files\src\js.

2. Встроить билд в Creatio 

Для этого необходимо в файле bootstrap.js (пакета Creatio, куда Вы хотите загрузить веб-компонент) настроить конфиг с указанием пути к билду.

Настройка конфига
(function() {
    require.config({
        paths: {
            "angular-element-component": Terrasoft.getFileContentUrl("MyPackageName", "src/js/angular-element-component.js"),
        }
    });
})();

Для загрузки bootstrap укажите путь к данному файлу. Для этого создайте descriptor.json в Название пользовательского пакета\Files.

descriptor.json
{
    "bootstraps": [
        "src/js/bootstrap.js"
     ]
}

Выполните загрузку из файловой системы и компиляцию.

3. Выполнить загрузку компонента в необходимой схеме/модуле 

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

Выполнение загрузку компонента
define("MyModuleName", ["angular-element-component"], function() {

4. Создать HTML-элемент и добавить его в модель DOM 

Пример добавления пользовательского элемента angular-element-component в модель DOM страницы Creatio
/**
 * @inheritDoc Terrasoft.BaseModule#render
 * @override
 */
render: function(renderTo) {
   this.callParent(arguments);
   const component = document.createElement("angular-element-component");
   component.setAttribute("id", this.id);
   renderTo.appendChild(component);
}

Работа с данными 

Передача данных в Angular-компонент выполняется через публичные свойства/поля, помеченные декоратором @Input.

Важно. Описанные в camelCase свойства без указания в декораторе явного имени будут переведены в HTML-атрибуты в kebab-case.

Пример создания свойства компонента (app.component.ts)
@Input('value')
public set value(value: string) {
   this._value = value;
}
Пример передачи данных в компонент (CustomModule.js)
/**
 * @inheritDoc Terrasoft.BaseModule#render
 * @override
 */
render: function(renderTo) {
   this.callParent(arguments);
   const component = document.createElement("angular-element-component");
   component.setAttribute("value", 'Hello');
   renderTo.appendChild(component);
}

Получение данных от компонента реализовано через механизм событий. Для этого необходимо публичное поле (тип EventEmiter<T>) пометить декоратором @Output. Для инициализации события необходимо у поля вызвать метод emit(T) и передать необходимые данные.

Пример реализации события в компоненте (app.component.ts)
/**
 * Emits btn click.
 */
@Output() btnClicked = new EventEmitter<any>();
 
/**
 * Handles btn click.
 * @param eventData - Event data.
 */
public onBtnClick(eventData: any) {
   this.btnClicked.emit(eventData);
}

Добавьте кнопку в angular-element.component.html.

Пример добавления кнопки в angular-element.component.html
<button (click)="onBtnClick()">Click me</button>
Пример обработки события в Creatio (CustomModule.js)
/**
 * @inheritDoc Terrasoft.Component#initDomEvents
 * @override
 */
initDomEvents: function() {
   this.callParent(arguments);
   const el = this.component;
   if (el) {
      el.on("itemClick", this.onItemClickHandler, this);
   }
}

Использование Shadow DOM

Некоторые компоненты, созданные с помощью Angular и встроенные в Creatio могут быть сконфигурированы так, чтобы реализация компонента была закрыта от внешнего окружения так называемым Shadow DOM.
Shadow DOM — это механизм инкапсуляции компонентов внутри DOM. Благодаря ему, в компоненте есть собственное «теневое» DOM-дерево, к которому нельзя просто так обратиться из главного документа, у него могут быть изолированные CSS-правила и т. д.

Для использования Shadow DOM необходимо в декоратор компонента добавить свойство encapsulation: ViewEncapsulation.ShadowDom.

angular-element.component.ts
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
 
@Component({
   selector: 'angular-element-component',
   templateUrl: './angular-element-component.html',
   styleUrls: [ './angular-element-component.scss' ],
   encapsulation: ViewEncapsulation.ShadowDom,
})
export class AngularElementComponent implements OnInit {
}

Создание Acceptance Tests для Shadow DOM

Shadow DOM создает проблему для тестирования компонентов в приложении с помощью приемочных cucumber тестов. К компонентам внутри Shadow DOM нельзя обратится через стандартные селекторы из корневого документа.
Для этого необходимо использовать shadow root как корневой документ и через него обращаться к элементам компонента.

Shadow root — корневая нода компонента внутри Shadow DOM.
Shadow host — нода компонента, внутри которой размещается Shadow DOM.

В классе BPMonline.BaseItem реализованы базовые методы по работе с Shadow DOM.

Важно. В большинстве методов необходимо передавать селектор компонета, в котором находится Shadow DOM —shadow host.

Метод Описание
clickShadowItem Нажать на элемент внутри Shadow DOM компонента.
getShadowRootElement По заданному css-селектору Angular компонента возвращает его shadow root, который можно использовать для дальнейших выборок элементов.
getShadowWebElement Возвращает экземпляр элемента внутри Shadow DOM по заданному css-селектору. В зависимости от параметра waitForVisible ожидает его появления либо нет.
getShadowWebElements Возвращает экземпляры элементов внутри Shadow DOM по заданному css-селектору.
mouseOverShadowItem Навести курсор на элемент внутри Shadow DOM.
waitForShadowItem Ожидает появления элемента внутри Shadow DOM компонента и возвращает его экземпляр.
waitForShadowItemExist Ожидает появления элемента внутри Shadow DOM компонента.
waitForShadowItemHide Ожидает скрытие элемента внутри Shadow DOM компонента.

К сведению. Примеры использования методов можно найти в классе BPMonline.pages.ForecastTabUIV2.