LoginSignup
52

More than 5 years have passed since last update.

Angular.js レンダリング処理をカスタマイズする

Last updated at Posted at 2013-12-09

Angular.jsを使っていてこんな風に思ったことはないだろうか。

  • ループ内のDOMをテンプレート化したいな
  • レンダリング処理を部品化してカスタムタグで呼び出したい
  • jQuery UIとかを使いたいけど、どこで処理すればいいのか(document.readyだと新規追加したものに適用されない)

そんな願いをかなえるのがAngular.jsのdirectiveです。directiveは、一言で言えば ModelとDOMを受け取ってレンダリング処理(compile)を行う関数 で、これを使って上記のようなカスタマイズをすることができます。

そんなわけで、作成したサンプルはこちら。このコードを使って解説をしていきたいと思います。

テンプレート化する

テンプレート化は、以下のようにかけます。
<disp-destination ></disp-destination>、これがdirectiveを呼び出しレンダリングしている箇所です。何このタグ?というのは次のセクションで語るとして、ここではscriptタグで宣言したテンプレートをdirectiveでどう使っているのかを解説します。

・・・
    <div id="itemList">
        <div class="item" ng-repeat="d in destinations" >
            <disp-destination ></disp-destination><!-- directiveによるレンダリング -->
        </div>
    </div>
・・・
<script type="text/html" id="template"><!-- こちらがテンプレート -->
    {{d.name}}:{{d.address}}
    <div class="complete" style="font-size:8px" range-select ></div>
</script>

こちらがdirectiveの宣言。moduleにdispDestinationを追加しています。directiveではテンプレートを返すことができ、ここでjQueryで取得したテンプレートのhtml DOMを渡しています。これでテンプレートによるレンダリングが行われるのだ。

var santaApp = angular.module("santaApp",[]);
・・・
santaApp.directive("dispDestination",function(){
    //テンプレートをid指定で取得、htmlを設定
    return {
      restrict: 'E',
      template: $("#template").html()
    };
})

そしてお気づきかもしれないが、directiveは定義するときはキャメルケースだが使うときはハイフンつなぎになる(customDirectiveならcustom-directiveになる)。この点は要注意。

カスタムタグを使いたい

先ほどのJavaScriptコードでrestrict 'E'という記載がありましたが、これがキモ。restrictがdirectiveの指定方法を表しています(EだとElement、つまりタグで判断する)。
指定の方法はいくつかあり下記にまとめていますが、正直AかEしか使わないので他のrestrictについてはドキュメント上存在するが動作は未確認。。。

restrict directiveの指定方法
A(デフォルト) 属性として指定: <div custom-directive></div>
E タグとして指定: <custom-directive></custom-directive>
C クラスとして指定: <div class="custom-directive"></div>
M コメントとして指定: <!-- directive: custom-directive -->

このrestrictをうまく使用することで、モジュール化を行うとともに可読性の高いViewを作成できます。

レンダリングしたDOMを処理したい(jQuery UIなど)

今回はjQuery UI からSliderを使用しました。ポイントは以下2点。

1 directiveを使用し、DOM要素をSliderオブジェクトにする( $("xxx").slider() を行う)
2 Sliderをスライドさせたイベントを検知し、scope内のModelに反映する

directiveでは以下の変数を受け取ることができ、elementがあれば①はもうクリア可能です。問題は②のイベントハンドリングで、ここはSlider側で検知したイベントからAngular内のscopeを更新する必要があります。

directiveで受け取れる変数
* scope:グローバルでなくローカルスコープなので注意。この場合、ループ中のスコープになるため d というループ中で使用している変数にアクセスできる
* element:directiveが付与されたDOM
* attr:指定された属性の情報

解決策として今回とったのは、Angularのscopeにあらかじめ値同期用の関数を用意しておき、それをイベント処理内で呼び出す方法です。
→ng-Modelを設定したフォーム項目を用意しておいてそこの値を変えればOKかなと思ったがうまくいかなかった。他にいいやり方があったら教えてください。。。

var santaApp = angular.module("santaApp",[]);
santaApp.controller("santaAppCtrl",function($scope){
    $scope.title  = "Santa Claus Delivery List";
    $scope.destinations = 
        [new Destination("Mike","NewYork"),
         new Destination("Bekky","Japan"),
         new Destination("Bob","Africa")];
    //受け取ったindex要素にcompleteの値を反映する関数
    $scope.applyComplete = function(index,complete){
        $scope.destinations[index].complete = complete;
    }
})

santaApp.directive("rangeSelect",function(){
    return function(scope, element, attrs){
        $(element[0]).slider({
              range: "min",
              value: scope.d.complete,
              min: 1,
              max: 100,
              slide: function( event, ui ) {
                  var index = $("#itemList .complete").index(this);
                  var scope = angular.element($("#delivery")).scope(); //グローバルスコープ取得
                  scope.applyComplete(index,ui.value); //slideイベントからscope内の値を更新
              }
        });
    }
})

以上が、Angular.jsにおけるレンダリング処理のカスタマイズ方法です。ここを知っていると大分活用の幅が広がるので、ぜひお試しされたし!
公式ドキュメントはこちら。英語だが、結構丁寧でわかりやすい。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
52