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 のコードは対象スコープの外なので動作しません。呼び出し元に影響していないのがわかります。
こんな感じで動いてくれています。
次はもう少し 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 でねじ伏せてみた。
結果、これで動いた。。。なんかもうすごい。
まとめ
ふと思ってやってはみたものの、いけるんだなって思った。
ちょっとしたディレクティブなら、ちゃちゃっと読み込めてしまいますよ。
絶対にやらないと思うけど。