Custom Elementsの属性を変化させた時に、その描画が変化しない

# はじめに
　Web Componentsで作成するCustom Elementsを動的に配置した時に、それぞれのElementの値が更新されないという問題に直面しました。解決するのが少しだけ大変でしたので、解決策を共有したいと思います。英語が読める人は「参考」の先のリンクを参照してもらえると、この内容について詳しく知ることが出来ると思います（日本語のページもたくさんあります）。

# やりたいこと
　自分で作成したCustom Elementsを、ウェブページに動的に配置する際に、Elementの属性（attribute）に値を入れると、Elementの描画の挙動が変わって欲しい。ざっくりいうと以下のコードがその下に貼り付ける画像のように動いて欲しい。

## コード（こんな感じで動いて欲しい！)

```html:index.html
<!DOCTYPE html>
<html lang='en'>

<head>
  <meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
  <script type="text/javascript" src="./example-template.js" defer></script>
  <script type="text/javascript" src="./index.js" defer></script>
  </script>
  <!--　Custom Elementを引っ張ってくる "defer"　を書かないと動かない  -->
  <title>custom elements example</title>
</head>

<body>
  <div id="parent">
    <!-- ここに動的にcustom elementsを入れたい -->
  </div>
  <!-- Custom Elementの利用。nameTitleとplaceHolderは作成した新しい属性（Attribute） -->
  <example-template name_title="Text form" plac="Please type your name"></example-template>

</body>

</html>
```

```javascript:index.js
var parentEle = document.getElementById("parent");
var childElement = document.createElement('example-template');
childElement.setAttribute('name_title', 'Dynamic Insert');
childElement.setAttribute('plac', "値が入って欲しいのだ。");
parentEle.appendChild(childElement);
```

## 動作イメージ（こんな感じで動いて欲しい！)
<img width="683" alt="スクリーンショット 2020-07-03 18.29.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81253/41aca086-7b89-4f02-2a7f-ea14133f8077.png">

# ぶち当たった問題
　ところが、index.htmlのjavascriptを書いただけでは、このように動かないんですね、実際にどうなっているかというと、以下みたいな感じです。Text formという最初からbodyに入っているElementは正しく想定どおりに描画されていますが、動的に入れたElementは正しく描画されません。具体的には、属性に入れた値がきちんと入っていないですね。

## 動作結果（ちゃんと動かないよ！）
<img width="572" alt="スクリーンショット 2020-07-03 18.36.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81253/e503531b-5f6a-7541-6719-6ef69c66a975.png">


ちなみに、この時の生成されているHTMLはこんな感じです。Chromeの検証で引っ張ってきています。
![image.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81253/2dbd84aa-24a1-ee95-2295-1c33a16929e8.jpeg)


# 解決方法
　解決するには、実はcustom elementの方にコードを書かないといけません。何が問題かと言いますと、作成したcustom elemetがattributeの変更を検出して、値を入れるということができていないんですね。そのため、attributeが変化したことを検出して（attributeChangedCallback)、値を入れないといけないんです。実際に書いたコードは以下です。コメントに説明を入れています。


```javascript:example-template.js


class ExampleTemplate extends HTMLElement {
    constructor() {
        // superは必ず一番最初に呼び出される。
        super();
        // これより下にCustom Elementに関する記述を行う

        //shadow rootの作成。これが今回の一番上のノード（ルート）
        const shadow = this.attachShadow({ mode: 'open' });

        // 今回はひとまとまりをsectionで包む
        const wrapper = document.createElement('section');

        //* Sectionの下に並べるElementsの情報を入れていく *//
        // h2タグ（タイトル）
        const subtitle = document.createElement('h2');
        subtitle.setAttribute('class', 'subTitle');
        const titleSub = this.getAttribute('name_title');    // nameTitleというAttributeの値を取得
        subtitle.textContent = titleSub;                    // nameTitleの値をh2タグで表示

        // form & input (テキストフィールド)
        const formF = document.createElement('form');
        const textField = document.createElement('input');

        textField.setAttribute('type', 'text');             // inputのtype指定
        textField.setAttribute('class', 'textField');       // classをtextFieldに
        const contents = this.getAttribute('plac');  // placというAttributeの値を取得
        textField.setAttribute('placeholder', contents);    // input(type=text)のplaceholderにplacの値を代入
        formF.appendChild(textField);                       // formの下にtextFieldをネストする

        // Shadow domにCSSを適用する
        const style = document.createElement('style');
        console.log(style.isConnected);
        style.textContent = `
        .textField {
            width: 500px;
            height: 100px;
        }
        `;                                                  //今回はtextFieldの幅と高さを指定した。

        // shadow domを一番上にして、上で作ったものをネストしていく
        // shadow |- style
        //        |- wrapper |- subtitle
        //                   |- formF
        shadow.appendChild(style);
        shadow.appendChild(wrapper);
        wrapper.appendChild(subtitle);
        wrapper.appendChild(formF);
    }

    // Attributeが変更された時に行う処理の定義
    static get observedAttributes() { return ["name_title", "plac"] };    //"nameTitle"と"plac"が変わることを監視させる

    // observedAttributesで監視しているattributeが変わったら呼ばれる関数、attrに呼ばれたattribute名、newValには新しく入った値が入る
    attributeChangedCallback(attr, oldVal, newVal) {

        //このconsoleはデバッグ用
        console.log('my-el attribute changed', attr);
        console.log('new value is ', newVal);

        //attrの値によって分ける
        if (attr === 'name_title') {            // attrがname_titleのとき
            // Create title holder
            this.shadowRoot.querySelector('.subTitle').textContent = newVal;    //.subTitleのtextContentにnewVal（入力値）を入れる
        } else if (attr === 'plac') {
            // Create description holder
            this.shadowRoot.querySelector('.textField').setAttribute('placeholder', newVal);    //.textField(input element)にnewVal（入力値）を入れる
        }
    }
}
// 新しいElementの定義
customElements.define('example-template', ExampleTemplate);
　
```

# 参考
[1] [Using custom elements, MDN Web Doc](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) 
[2] [Eric Bidelman, カスタム要素 v1: 再利用可能なウェブ コンポーネント, Google Developers](https://developers.google.com/web/fundamentals/web-components/customelements?hl=ja)
[3] [Custom elements, javascript.info](https://ja.javascript.info/custom-elements)
[4] [HTML Living Standard](https://html.spec.whatwg.org/multipage/custom-elements.html)
# 問合せ先
akira.kashihara@hotmail.com