Can I do web?

AngularJS 中心の不定期ブログ

Web Components ライクに Directive を読み込んでみた

Advent Calendar 8日目だー!

ということで。


そもそもできるのかなーと、ふと思ってやってみた。
完全にネタ回です!


まずは呼び出す側のHTMLがこちら。

<!doctype html>
  <head>
    <meta charset="utf-8">
    <title>demo</title>
    <link rel="stylesheet" href="styles/main.css">
    <link rel="import" href="clickCounter.html" /><!-- ① -->
    <link rel="import" href="inputText.html" /><!-- ② -->
  </head>
  <body>
    <p>クリックカウンター</p>
    <click-counter></click-counter><!-- ③ -->
    <div ng-init="myModel='foo'">{{ myModel }}<div>
    <click-counter></click-counter><!-- ③ -->

    <p>文字入力</p>
    <input-text></input-text><!-- ④ -->
    <div ng-init="myModel2='bar'">{{ myModel2 }}<div>
    <input-text></input-text><!-- ④ -->
</body>
</html>

①、②で2種類のコンポーネントを読み込んでいます。
見たらわかると思いますが、①は click-counter 要素、②は input-text 要素を定義するためのものです。


まずは①のコードから見てみます。
機能としてはボタンをクリックで、カウントアップするだけのものです。


clickCounter.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>clickCounter</title>
</head>
<body>
    <script src="bower_components/angular/angular.js"></script>
    <script src="scripts/clickCounter.js"></script>
    <script>
      var element = angular.element(document);
      angular.bootstrap(element.find('click-counter'), ['clickCounterApp']);
    </script>
</body>
</html>


clickCounter.js

'use strict';
// clickCounterApp module 作成
var app = angular.module('clickCounterApp', []);

// clickCounter directive 作成
app.directive('clickCounter', [function(){
  return {
    scope: {},
    restrict: 'E',
    controller: function($scope){
      $scope.count = 0;

      $scope.click = function(){
        $scope.count ++;
      }
    },
    templateUrl: 'clickCounterTmpl.html'
  };
}]);


さらに、テンプレートを外部ファイル化。 clickCounterTmpl.html

<div>
  <button ng-click="click()">click</button>
  <span ng-bind="count"></span></div>

①についてはこんな感じの構成でやってみました。
ポイントは、 angular.bootstrap を使って AngularJS をブートしてあげること。
原則 ng-app は1カ所のみ使用するのがルールですけど、こうすることで、無視できます。
複数呼び出すと警告はでます。
③を2カ所で呼んでいて、その間のAngularJS のコードは対象スコープの外なので動作しません。呼び出し元に影響していないのがわかります。

f:id:canidoweb:20141208224102p:plain

こんな感じで動いてくれています。


次はもう少し Web Components の仕様と戯れてみます。
以下が、②の構成。
機能としては、テキストボックスに入力した文字を出力するだけ。


inputText.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>inputText</title>
</head>
<body>
    <template>
      <input type="text" ng-model="myText">{{ myText }}
    </template>
    <script src="bower_components/angular/angular.js"></script>
    <script src="scripts/inputText.js"></script>
    <script>
      var element = angular.element(document);
      angular.bootstrap(element.find('input-text'), ['inputTextApp']);
    </script>
</body>
</html>


inputText.js

'use strict';

var app = angular.module('inputTextApp', []);

app.directive('inputText', ['$compile', function($compile){
  return {
    scope: {},
    restrict: 'E',
    template: '<div></div>',
    link: function($scope, iElm, iAttrs, controller) {
      var _self = angular.element(document.currentScript.ownerDocument);
      var template = _self.find('template');

      var shadow = iElm[0].createShadowRoot();
      shadow.innerHTML = template.html();

      $compile(shadow)($scope);
    }
  };
}]);


呼び出し方については、①と同じです。あいだに動かないmodelを定義しているのも一緒です。
やってみたことは、ディレクティブ作る用に template を書くプロパティがあるにもかかわらず、template 要素を使ってみたくて使っちゃったこと。
使ってみたかっただけなので、やいやい言われてもどうしようもありません。無駄の極みです。
そして、template 要素から取得したDOM を Shadow DOM につっこんで、そいつを $compile でねじ伏せてみた。
結果、これで動いた。。。なんかもうすごい。

f:id:canidoweb:20141208224103p:plain

まとめ

ふと思ってやってはみたものの、いけるんだなって思った。
ちょっとしたディレクティブなら、ちゃちゃっと読み込めてしまいますよ。


絶対にやらないと思うけど。