NXとWebコンポーネントを使用したフロントエンドフレームワーク間の共有コンポーネント


これは3部ガイドの2番目です.
  • Part 1 - Project Setup and Introduction to Web Component

  • Part 2 - Add Custom Style and Property Binding
  • Part 3 - Output Event and Allow Retrocompatibility.

  • パート2 -カスタムスタイルとプロパティのバインドを追加
    パート1では、Webコンポーネント、それらが何であるか、どのように使用するかを紹介しました.また、共有のWebコンポーネントライブラリと一緒に角度と反応プロジェクトをNXのワークスペースを作成し、設定します.属性に“listens”するカスタム要素を作成しましたtitle と変更DOM したがって内容.この2番目の部分では、スタイリングによってさらに進んで、Webコンポーネントにプロパティを割り当てます.

    1 . Webコンポーネントのスタイル
    私たちのWebコンポーネントが動作しますが、彼らは現在、非常にフラットです.それにいくつかのCSSを追加するのは素晴らしいことだ.CSSクラスをタイトルに追加し、グローバルなスタイルシートをいくつかのルールで作成し、それが動作します.しかし、Webコンポーネントの背後にあるアイデアは、自動で十分な独立した要素を作成し、“外”から何かを必要としません.グローバルCSSクラスの追加は、ページ上のすべての要素に影響を与え、すぐにルールに満ちた巨大なファイルになります.
    うまくいけば、Webコンポーネントを使用することができますShadow DOM , これにより、マークアップ構造、スタイル、および動作を隠して、ページ上の他のコードから分離することができます.おかげで、要素は衝突しません、そして、コードは単純できれいに保たれます.
    私たちの図書館(パート1にセットアップ)に戻って、我々のタイトル構成要素を編集するか、新しいタイトル要素を作成しましょう.
    もしまだ私たちが働いていることがあるなら、このように見えます(ログを削除しました).
    export class DemoTitleElement extends HTMLElement {
        public static observedAttributes = ['title'];
    
        attributeChangedCallback(name: string, old: string, value: string) {
          this.innerHTML = `<h1>Welcome From ${this.title}!</h1>`;
        }
    }
    
    customElements.define('demo-title', DemoTitleElement);
    
    上の例では、属性のすべての更新に対して、要素内のHTML全体を置き換えただけですtitle :
        attributeChangedCallback(name: string, old: string, value: string) {
          this.innerHTML = `<h1>Welcome From ${this.title}!</h1>`;
        }
    
    ご存知かもしれませんが、この方法では<style> タグ.したがって、手動でクラスを書き、いくつかのグローバルルールを追加しない限り、新しいスタイルを作成できません.また、我々の特質のあらゆる変化のためにすべての内容を再現するので、このアプローチはパフォーマンスでかなり貧弱です.いくつかの種類のテンプレートを持つことは素晴らしいです.
    まあ、そこにあることが判明template 我々のニーズに合う要素!テンプレートはDOMではレンダリングされませんが、JavaScript経由で参照することもできます.私たちは、私たちの影DOMの中にこのテンプレートを一度クローン化して、それを我々の構成要素の向こう側にアクセスできます.
    私たちの構成要素クラスの外で1つをつくって、我々が好きであるスタイルを割り当てましょう.
    const template = document.createElement('template');
    template.innerHTML = `<style>
        h1 {
          color: red;
        }
      </style>
      <h1>Welcome From <span id="title"></span>!</h1>`;
    
    あなたは私がspanid ここでは、動的なタイトルを書いてみたい.それは完全にテンプレートを再作成することなく私たちのDOMを更新するために便利になります.
    今すぐ私たちのコンポーネントconstructor , シャドウDOMを付け、テンプレートを追加します.
    export class DemoTitleColoredElement extends HTMLElement {
        public static observedAttributes = ['title'];
    
        constructor() {
          super();
          this.attachShadow({ mode: 'open' });
          this.shadowRoot.appendChild(template.content.cloneNode(true));
        }
    }
    
    ここに我々が追加されます
  • superconstructor of HTMLElement
  • attachShadow シャドウDOMツリーを指定した要素にアタッチし、そのシャドウルートへの参照を返します.つの異なることができますencapsulation modes . open シャドウルートの要素は、ルートの外側のJavaScriptからアクセス可能ですclosed JavaScript外部からのクローズドシャドウルートのノードへのアクセスを拒否する
  • this.shadowRoot.appendChild 私たちはシャドウルートにテンプレートを加えて、使用していますtemplate.content.cloneNode(true) テンプレートで定義したDOM要素をすべてクローニングします.
  • さて、これはずっと良いです.私たちのテンプレートは、1回クローンされshadowRoot コンポーネントの内部.
    ときに属性を変更すると、我々は現在、テンプレート内の興味を更新することができます.我々が気にかけるだけでtitle 属性は、単に以下を追加することができます.
        attributeChangedCallback() {
          this.shadowRoot.getElementById('title').innerHTML = this.title;
        }
    
    以下のようなコンポーネントで終わります.
    const template = document.createElement('template');
    template.innerHTML = `<style>
        h1 {
          color: red;
        }
      </style>
      <h1>Welcome From <span id="title"></span>!</h1>`;
    
    export class DemoTitleColoredElement extends HTMLElement {
        public static observedAttributes = ['title'];
    
        constructor() {
          super();
          this.attachShadow({ mode: 'open' });
          this.shadowRoot.appendChild(template.content.cloneNode(true));
        }
    
        attributeChangedCallback() {
          this.shadowRoot.getElementById('title').innerHTML = this.title;
        }
    }
    
    customElements.define('demo-title-colored', DemoTitleColoredElement);
    
    いいねこの新しい要素をライブラリのエクスポートに追加し、角度および/または反応プロジェクトでそれを試してください.私は以前に作成した古いタイトルと下部に新しいものを追加しました.私たちが見ることを期待するのは、我々の新しいタイトルが赤でなければならなくて、影響を及ぼさない間、黒人のままでいる我々の古いタイトルですh1 その外に
    <demo-title [title]="'Angular'"></demo-title>
    <demo-title-colored [title]="'Angular'"></demo-title-colored>
    

    すごい!私たちのスタイルは、コンポーネントにのみ適用されます!

    2 . Webコンポーネントへのオブジェクトのパス
    我々がしたい1つの一般的なことは、単純なストリングだけでなく、我々のウェブ構成要素にオブジェクトを渡すことです.
    WebコンポーネントからattributeChangedCallback and observedAttributes 排他的な作品attribute , 可能な解決策は、オブジェクトをstringifyすることになっていて、属性 data-* HTMLは有効なままです.
    まずこれを試してみましょうobject 当社のWebコンポーネントには、単にその人の名前を表示します.

    属性を通しての2.1の通過オブジェクト
    当社のライブラリでは、カスタム属性をdata-person
    export class DemoObjectElement extends HTMLElement {
        public static observedAttributes = ['data-person'];
    
        attributeChangedCallback(name: string, old: string, value: string) {
          console.log(`Attribute ${name} value:`, value);
        }
    }
    
    customElements.define('demo-object', DemoObjectElement);
    
    この新しい要素をライブラリのエクスポートに追加し、角で最初に使いましょう.
    内部app.component.ts このオブジェクトを作成します
      person = {
        firstName: 'Jack',
        lastName: 'Doe'
      }
    
    移動するapp.component.html Webコンポーネントにオブジェクトを追加して割り当てます.使用する角度を指定する必要がありますattribute との結合をprefixすることでattr.person
    <demo-object [attr.data-person]="person"></demo-object>
    
    Webコンポーネントログは次のようになります.

    Attribute data-person value: [object Object]


    それは私たちが望むものではない.オブジェクト全体を受け取るには、JSON.stringify 最初にJSON.parse ...
    角度を更新app.component.ts 次の行を指定します.
    JSON = JSON;
    
    テンプレート
    <demo-object [data-person]="JSON.stringify(person)"></demo-object>
    
    また、Webコンポーネントログを更新することもできますobject
    console.log(`Attribute ${name} value:`, JSON.parse(value));
    
    期待通りにオブジェクトを表示します.

    Attribute data-person value: {"firstName":"Jack","lastName":"Doe"}


    反応のために、それは非常に類似しています.オープンapp.tsx 次のように追加します.
      return (
        <div className={styles.app}>
          <demo-object dataPerson={JSON.stringify(person)}/>/>
        </div>
      );
    
    私たちのWebコンポーネントは、“ネイティブ”のHTML要素は、単に割り当てdataPerson 属性を変更するdata-person .
    オブジェクトを属性として渡す場合は、小さなオブジェクトを割り当てたい場合に十分です.しかし、理想的には、私たちは、属性を汚染したくないです、そして、最悪の場合、文字列化されて、あらゆる変化でパースされる必要があるオブジェクトを割り当てます.代わりにプロパティとして渡すことについてはどうですか?まあ、我々はすることができますが、それは少し難しいです.

    2.2プロパティ経由でオブジェクトを渡す
    Webコンポーネントにプロパティを受信できるようにするには、まずいくつかのコードを変更する必要があります.Webコンポーネントのライフサイクル機能attributes そこで、手動でプロパティの変更を検出する必要があります.また、最後の部分で述べたように、Webコンポーネントは基本的なHTMLのように消費されます.HTML要素に渡すプロパティは、使用するフレームワークによって異なります.
    変更中ですが、その人の名前を表示し、ログだけではないようにしましょう.そうするために、我々のコンポーネントでテンプレートをつくってください.
    const template = document.createElement('template');
    template.innerHTML = `
    <style>
        .name {
            font-weight: bold;
        }
    </style>
    <p>
        <span class="name">first name</span>
        <span id="firstName"></span>
    </p>
    <p>
        <span class="name">last name</span>
        <span id="lastName"></span>
    </p>`;
    
    コンポーネントのコードを変更しましょう.という名前のプロパティを渡すperson Webコンポーネントに.もし我々が今までやっているようなことをすれば、次のようになるだろう.
    export class DemoProfileElement extends HTMLElement {
        public static observedAttributes = ['person'];
    
        constructor() {
          super();
          this.attachShadow({ mode: 'open' });
          this.shadowRoot.appendChild(template.content.cloneNode(true));
        }
    
        attributeChangedCallback() {
          this.update(this.person);
        }
    
        update(person: {firstName: string, lastName: string}) {
          this.shadowRoot.getElementById('firstName').innerHTML = person.firstName;
          this.shadowRoot.getElementById('lastName').innerHTML = person.lastName;
        }
      }
    
      customElements.define('demo-profile', DemoProfileElement);
    
    しかし、あなたのタイプスクリプトが言うかもしれません.person はHTML属性ではないので、this.person が定義されていない.加えてattributeChangedCallbackobservedAttributes = ['person']; 存在しない属性を監視できません.
    これを修正して動作させるために、我々は今までしたことを忘れる必要があり、変更を検出する方法を実装する必要があります.あなた自身でそれを修正しようとするだけで解決策をスクロールすることができます.
    私たちが選んだクラスのプロパティを必要とする_person , とget/set それはその財産に割り当てられる
    export class DemoProfileElement extends HTMLElement {
        _person = {
          firstName: '',
          lastName: '',
      };
    
      get person(){
        return this._person
      }
      set person(value: {firstName: string, lastName: string}){
        this._person = value;
        this.update(this._person);
      }
    
      constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.appendChild(template.content.cloneNode(true));
      }
    
        update(person: {firstName: string, lastName: string}) {
          this.shadowRoot.getElementById('firstName').innerHTML = person.firstName;
          this.shadowRoot.getElementById('lastName').innerHTML = person.lastName;
        }
    }
    
    customElements.define('demo-profile', DemoProfileElement);
    
    はい、プロパティを更新したときにget/setをチェックして、それに応じてDOMを更新します.
    これを角度で試してみましょうが、この新しい要素をライブラリのエクスポートに追加することを忘れないでください!
    コンポーネントに入力を渡すとき、角度はそれをその要素の特性として割り当てます.私たちは多くを変更する必要はありません.
    あなたの角度のアプリでは、この行を追加します.
    <demo-profile [person]="person"></demo-profile>
    
    そして、それ!

    物事が少し異なっているところに反応しよう.JSX属性としてパスをプロップ<demo-profile person={person} /> は動作しません.WebコンポーネントをHTML要素として扱う必要があります.だから、参照を取得し、プロパティに割り当てます.
    あなたの反応アプリでは、我々のコンポーネントへの参照を追加し、初期化の後、person Webコンポーネントへのプロパティー
    import { DemoCounterElement, DemoProfileElement } from '@demo-shared/demo-library';
    
    export function App() {
      const person = {
        firstName: 'Jack',
        lastName: 'Doe'
      }
      const profile = useRef<DemoProfileElement>(null);
    
      useEffect(function () {
        if(profile.current) {
          profile.current.person = person 
        }
      }, []);
    
      return (
        <div className={styles.app}>
          <demo-profile ref={counter}/>
        </div>
      );
    
    
    これはすべての反応のためですプロジェクトを起動し、同じ結果が表示されます!

    素晴らしい、我々はほとんどすべての我々はしたいと思ったが、まだいくつかのことを追加する必要があります.
    最後の部分では、Webコンポーネントでディスパッチされるカスタムイベントを追加し、古いブラウザでコンポーネントを使用するPolyfillを追加します.
    ここでREPO全体を見つけることができます.
    typoまたはいくつかの問題を発見?
    あなたがtypo、改善されることができた文またはこのブログ柱で更新されるべき何かを見つけたならば、あなたはそれをGITリポジトリでアクセスして、プル要求をすることができます.直接あなたの変更を使用して新しいプル要求を開きます.