はまったりひらめいたり…とか…

Angularや.NETやAzureやその他色々。

Angular Elementsで作成したWeb Componentsの設定情報をプレーンなJSから変更する

はじめに

この記事内のアプリケーションは下記バージョンで構成しています。

  • Angular CLI: 10.0.1
  • Angular: 10.0.2

この記事を参照されるタイミングによっては

サンプルコードが動作しなかったりする可能性がありますのでご留意くださいませ。

そもそもなにをしたいのか

Microsoft GraphはGraph ToolkitというWeb Componentsを公開していまして

オリジナルでそれを作ってみたいなー。というのが発端です。

今回の記事はその前哨戦。

Web Componentsを作ってみてハマった箇所の備忘録。といった感じですね。

Angular Elements

Angularを使用している場合は、Angularで作成したComponentをWebComponentsでパッケージングしてくれる

Angular Elementsというものが存在します。Angularで諸々なれている自分はこれを使うのが一番手っ取り早そうです。

基本的な使い方などは、👆のドキュメントを参考にすれば良いと思うので割愛します。

最終的に下記のようにAngularを使用していないようなプレーンなHTMLで

作成したWebComponentsのセレクタを指定してAngularで作成したComponentを表示させることが出来ます

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="../../dist/custom-element/styles.css">
    <script src="../../dist/custom-element/custom-element.js"></script>
</head>
<body>

    <h1>Web Component (Costum Elements)</h1>
    <scl-custom-button></scl-custom-button>
    <scl-sample-form-group></scl-sample-form-group>
    <scl-router-page></scl-router-page>
</body>
</html>

👇

f:id:TakasDev:20200705111959p:plain

情報を設定する🤔

さて、Micorosoft Graphにアクセスするということは、ADのアプリケーション情報などが必要になります。

設定情報をWeb Components作成時点でビルドインしても良いのですが、それでは使い回しがしにくいです。

具体的には下図のような構成にしたいところですね。

f:id:TakasDev:20200705113551p:plain

ビルドされたソースはどうなっている?

ビルドされたパッケージ内のServiceクラスに値を直接外部から突っ込めるでしょうか?

下記のTSがビルドされたソースを整形して見てみます。

export class LibSettingsComponent implements OnInit {
...
 constructor(
    private service: LibStateService
  ) { }
...
  set setting(setting: { state: string, userId: string, userName: string }) {
    console.log('emit');
    this.service.setting(setting);
    console.log(this.service.state);
    this.dummyStSet = 'emit';
  }
}
        Oy), By = ((Iy = function() {
            function e(t) {
                _classCallCheck(this, e),
                this.service = t,
                this.dummySt = "dd"
            }
            return _createClass(e, [{
                key: "ngOnInit",
                value: function() {}
            }, {
            ...
            }, {
                key: "setting",
                set: function(e) {
                    console.log("emit"),
                    this.service.setting(e),
                    console.log(this.service.state),
                    this.dummyStSet = "emit"
                }
            }]),

this.serviceはfunctionの引数のtで更にそこの呼び出し元の…?

いずれにせよServiceのインスタンスに直接アクションをするのは多大な労力を伴いそうです。

設定用のComponentを用意する

Componentからのアクセスは簡易に行えます。なので設定用のComponentを作成する。というアプローチをとってみます。

中身が空の設定用のComponentを用意します。

設定用のComponentは@InputでSetterのみを提供し、Setterの実装内で設定情報Serviceを書き換えます。

具体的なソースコードは下記のとおりです。

色々視認やデバッグしやすいようにHTMLテンプレートにものを突っ込んでますが空で問題ないと思います。

import { Component, OnInit, Input } from '@angular/core';
import { LibStateService } from '../../services/lib-state.service';

@Component({
  selector: 'scl-lib-settings',
  template: '<div>{{dummySt}}</div>'
})
export class LibSettingsComponent {

  dummySt = 'bf';

  constructor(
    private service: LibStateService
  ) { }

  @Input()
  set setting(setting: { state: string, userId: string, userName: string }) {
    // ここで設定情報をServiceに反映する
    this.service.setting(setting);
    this.dummySt = 'emit';
  }
}

これでプレーンなHTMLのタグ上などで、settingプロパティに値を突っ込むことで設定情報が反映されるようになりました。

設定用のコンポーネントタグを書かなくて良いようにする

さて、設定を外から与えることができるようになりましたが

何も情報を出力しない設定用のコンポーネントをHTML上に書くのはなんともダサ味があります。

なので、スクリプトでも設定情報の反映ができるようにしたいところです。

そこで、

  1. 設定用のコンポーネントタグをcreateElementして
  2. 設定を行い
  3. 設定用のコンポーネントタグを削除する

こんな感じの設定スクリプトをAngular Elementsから提供することにします。

設定スクリプトは下記のようなものです。

class LibSettings {

    setSettings(data) {
        const settingDom = document.createElement('scl-lib-settings');
        document.body.appendChild(settingDom);
        settingDom.dummySt = 'hoge';
        settingDom.setting = data;
        document.body.removeChild(settingDom);
    }
}

作成した設定スクリプトをWebComponentsをビルドする際に一緒に吐き出したいので、angular.jsonをいじります。

    "custom-element": { // Angular Elementsのプロジェクト名
      ...
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            ...
            "scripts": [
               // 👇を追加
              "projects/custom-element/src/static-scripts/lib-settings.js"
            ]

これで、WebComponentsをパッケージングしたときに、同一ディレクトリに設定スクリプトも吐かれるようになりました。

f:id:TakasDev:20200705121442p:plain

使ってみる

設定スクリプトで設定が反映されたサービスのプロパティをアラート表示するボタンComponentを作成しました。

このボタンを押下することで設定情報の反映の確認を行ってみます。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="../../dist/custom-element/styles.css">
    <script src="../../dist/custom-element/custom-element.js"></script>
</head>
<body>

    <h1>Web Component (Costum Elements)</h1>
    <scl-custom-button></scl-custom-button>
    <scl-sample-form-group></scl-sample-form-group>
    <scl-router-page></scl-router-page>
    <script src="../../dist/custom-element/scripts.js"></script>
    <script type="text/javascript">
        // 設定スクリプトを使用して設定情報の反映
        const cl = new LibSettings();
        const setting = { state: 'HTMLから設定したよ!', userId: 'user', userName: 'hogehoge' }
        cl.setSettings(setting);
        //
    </script>
</body>
</html>

上記の状態で、アラートに「HTMLから設定したよ!」と表示されればOKです。

f:id:TakasDev:20200705122727g:plain

設定できました。

これで設定情報を外部から与えることができそうです。

おわりに

長い前哨戦が終わったので、次回はようやく目的だったオリジナルのGraph Toolkitの作成に入ってみます。

手を付けれていないのでいつになるかわかりませんが

今回検証を行ったソースコードは下記となります。

github.com

色々検証で試行錯誤したので少々とっちらかっているのはご容赦くださいませ…