Чтение данных из Angular веб-компонента Elements в обычном формате JavaScript - PullRequest
0 голосов
/ 16 июня 2020

Я создаю несколько WebComponent, которые я буду использовать в разных библиотеках / фреймворках, но в основном с обычным JavaScript. Я подумываю использовать для этого Angular Elements и построил образец, следуя нескольким документам.

Он отлично работает, со всеми встроенными натуральными Angular вкусностями. Но что Я борюсь с этим, чтение данных из веб-компонента из простого JavaScript, что-то вроде var result = myComponent.value.

  • Обратите внимание, что эти данные, которые я читаю, не примитив, ни одно значение. Это как объект со множеством деталей из комплекса WebComponent
  • Я бы хотел что-то вроде readonly property
  • Стандартные Angular документы для компонентов всегда используют @Output() с EventEmitter для этой цели. Но поскольку я буду использовать компонент как WebComponent вне Angular мира, я не могу (и не хочу) использовать этот подход. Кроме того, я не хочу уведомлять о каждом изменении значения, а просто при необходимости считываю значение

Если я пишу стандартный WebComponent простым JavaScript, я могу сделать это

class MyComponent extends HTMLElement {

    constructor() {
        super();
        this.name = 'Guest';
        this.count = 0;
    }

    // more code...

    // THIS result PROPERTY IS USED TO READ DATA FROM THE WEB COMPONENT
    get result() {
        return { name: this.name, count: this.count }; // <<== to read data
    }

    connectedCallback() {
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.innerHTML = `
            <style>
                styles....
            </style>
            <h1>MyComponent</h1>
            More HTML...
        `;
    }
}

customElements.define('my-component', MyComponent);

// Then I can use <my-component></my-component> in my application HTML
// And in the JavaScript code, I can read data from my-component as

const result = document.querySelector('my-component').result;
// which would be something like { name: 'Guest', count: 0 }

Есть ли способ сделать это в AngularElements?

1 Ответ

0 голосов
/ 17 июня 2020

Хорошо, у меня есть 2 обходных пути, чтобы справиться с этим! Они больше похожи на хаки, поскольку (1) они не выполняются стандартным способом Angular, и (2) могут сломаться в будущем из-за изменений реализации в пределах Angular. Ниже приведены 2 способа достижения output property для WebComponent, построенного с помощью Angular Elements

(я использую "@angular/core": "~9.1.11" и "@angular/elements": "^9.1.11")

  1. Мой коллега указал, что createCustomElement предоставляет только входные данные как свойства. Смотрите код здесь . Итак, hack - это пометить свойство как Input(), а затем просто прочитать его извне, как обычное свойство! Это очень неинтуитивно и не следует какой-либо стандартной семантике. И интересно, что Angular не жалуется, если у меня есть только getter для свойства, украшенного @Input()
  2. Лучший подход, как было указано Danny '365CSI' Engelman в комментариях, мы можем определить новое свойство в собственном элементе HTML, используя Object.defineProperty

Обратите внимание, что вместо варианта (1) лучшим подходом может быть улучшение / расширение logi c в Angular для включения других свойств из класса компонента в качестве свойств элемента, возможно, с новым настраиваемым декоратором, например @outputProperty.

Пример кода с реализацией обоих вышеуказанных подходов

// The component
import { Component, OnInit, ViewEncapsulation, Input, ElementRef } from '@angular/core';

@Component({
    // selector, templateUrl & styleUrls
    encapsulation: ViewEncapsulation.ShadowDom,
})
export class MyComponent implements OnInit {

    constructor(
        private element: ElementRef,
    ) {
        // THIS IS APPROACH 2 => we are defining a 'result2' property on the HTMLElement
        Object.defineProperty(this.element.nativeElement, 'result2', {
            get: () => { return { name: this.name, count: this.count }; },
            enumerable: true,
        });
    }

    public name = 'World';
    public count = 0;

    // More code for component functionalities and state management...

    // Just note that this does NOT work i.e. cannot read it from outside
    public get result0(): ElementMetadata {
        return { name: this.name, count: this.count };
    }
    public set result0(value) { console.log(`result value set to ${value}`); }

    // THIS IS APPROACH 1 => we are marking 'result1' with a getter, as @Input
    @Input()
    public get result1(): ElementMetadata {
        return { name: this.name, count: this.count };
    }
}

// The module (no changes made specifically for this)
export class AppModule {
    constructor(private injector: Injector) { }

    ngDoBootstrap() {
        const custElement = createCustomElement(
            MyComponent,
            { injector: this.injector },
        );
        customElements.define('my-component', custElement);
    }
}
...