LoginSignup
59

More than 5 years have passed since last update.

何となく分かった気持ちになる AngularJS のカスタムディレクティブ

Last updated at Posted at 2014-05-16

カスタムディレクティブを作るに当たって、覚えておくプロパティがいくつかある。

プロパティ一覧にすると

  • restrict
  • priority
  • template
  • templateUrl
  • replace
  • transclude
  • scope
  • controller
  • require
  • compile
  • link

となっている。

相変わらず公式の説明が取っつきづらく、かつ意外と奥が深いので、かみ砕きつつサンプルと一緒に載せておく。

ちなみに AngularJSアプリケーション開発ガイド はわりと役に立つので AngularJS をやるならおすすめの一冊です。 Ebook 版を買っていたのに書籍版も何故か買ってしまったので誰か書籍版を僕から買い取ってください。

restrict

HTML 側でディレクティブを呼び込むための形式を定義するプロパティ

複数指定もでき、 'EAC' のように指定する。

  • String
  • オプション
  • デフォルト: 'A'
    • 'E' : 要素名で指定
    <custom-directive></custom-directive>
    
    • 'A' : 属性で指定
    <div custom-directive>
    <div custom-directive="hoge"><!-- 値を設定する場合 -->
    
    • 'C' : クラスで指定
    <div class="custom-directive">
    <div class="custom-directive:hoge;"><!-- 値を設定する場合 -->
    
    • 'M' : コメントで指定(qiitaだと表示されないかも)
    <!-- directive: custom-directive -->
    <!-- directive: custom-directive hoge --><!-- 値を設定する場合 -->
    

priority

同一の HTML 要素に複数のディレクティブが設定されている場合の優先度を設定するプロパティ

同じ優先度であれば、処理順は不定になる

AngularJS が持つデフォルトのディレクティブにも priority が設定されているものがいくつかある。らしい。(別途調べる)

  • Number
  • オプション
  • デフォルト: 0

template

ディレクティブ指定した箇所に表示される HTML 要素を設定するプロパティ

文字列で HTML を書いて指定するやり方と、関数で指定するやり方がある。特に後者が分かりづらいのでサンプルを参照のこと。

  • String or Function
  • 必須(templateUrl が設定されている場合は不要)
  • サンプル

templateUrl

ディレクティブ指定した箇所に表示される HTML のテンプレートファイルの URL を設定するプロパティ

  • String
  • 必須(template が設定されている場合は不要)

replace

ディレクティブ指定した要素を。実体と置き換えるかどうかを設定するプロパティ

  • Boolean
  • オプション
  • デフォルト: false
  • サンプル

transclude

ディレクティブの指定された要素以下に子要素が存在した場合に、その要素をディレクティブの実体内へ読み込むかどうかを設定するプロパティ

言葉にすると分かりづらいのでサンプルを参照のこと。

  • Boolean
  • オプション
  • デフォルト: false
  • サンプル

scope

ディレクティブ内のスコープの扱いを設定するプロパティ

オブジェクトを渡した場合のみ、すこし特殊な扱いになるので注意。

  • Boolean or Object
  • オプション
  • デフォルト: false
    • false そのディレクティブが設定された場所の $scope が適用される
    • true そのディレクティブが設定された場所の $scope を継承して作られる新しい $scope

    親の $scope が同一であれば、各ディレクティブ間では同じ $scope のインスタンスが共有される
    - Object
    ディレクティブごとに隔離された $scope が生成される。この場合、親の $scope にアクセスすることはできない

    親から値を受け取ったりデータバインディングを行う場合は、ディレクティブに設定するアトリビュートを介して行う。難しいのでサンプルで確認してください

  • サンプル

controller

ディレクティブ内で独自の controller を持つ場合に設定するプロパティ

通常の controller と同じく ParentController > DirectiveController のように入れ子として扱われる。$scope については、上記 scope プロパティの設定に基づいたオブジェクトが適用される。

使用方法としては、通常の controller のように使う、もしくはディレクティブ同士のやりとりを行う API の実装など。他のディレクティブの require に指定される場合は設定が必須。

初期化は、ディレクティブごとに呼ばれる。また、DI(依存性注入) が適用されるので引数など注意。

  • Function or Array
  • オプション(他のディレクティブの require に指定してる場合は必須)
  • デフォルト: undefined
  • サンプル

require

ディレクティブの依存関係を設定するプロパティ

このプロパティで指定された controller は必ず controller を持っている必要があるので注意すること。

'^', '?' の関連づけ式を用いて挙動を変更することが可能。また '^?' のように複数指定できる。

  • String
  • オプション
    • '{directiveName}' 関連づけ式がない場合、同一のディレクティブからのみ依存するディレクティブのコントローラが探索される
    • '^{directiveName}' '^' の関連づけ式が指定された場合、本ディレクティブから祖先の要素に対してもコントローラーが探索される
    • '?{directiveName}' '?' の関連づけ式が指定された場合、対象のコントローラが見つからない場合でも例外は発生しなくなる。
  • サンプル

compile

ディレクティブのテンプレートがコンパイルされる段階で1度だけ呼ばれる。この1度だけっていうのは、ディレクティブの1インスタンスでは1度だけ、という意味なので2つ同じディレクティブが HTML に存在した場合はそれぞれが呼ばれるので注意。

よくよく考えてみると当たり前の話で、ディレクティブごとに transclude や attr などが変わるので当然別のインスタンスになる。1インスタンスでは複数回呼ばれることはないので、ng-repeat なんかの中で複数生成されるディレクティブに対して共通の処理を行う場合などに効果を発揮する

コンパイル段階なので、スコープを受け取ることはできない。が、下記のように pre, post 関数を戻り値にした場合にそれぞれの関数では受け取ることができる

関数の戻り値をオブジェクトにし、 { pre: fn() {}, post: fn() {} } と返すことでリンクの段階で呼ばれる関数を登録することができる。この関数内ではスコープを受け取ることができる

link と同時に指定することはできず、両方を指定した場合は compile で返した関数が呼ばれる

また公式で語られている注意点が2つあり

  1. このテンプレートインスタンスとリンクのインスタンスは、テンプレートが複製されると異なるオブジェクトになるかもしれません。 そのため、compile関数内で複製された全てのDOMノードに適用するDOM変換は危険以外の何物でもありません。 特に、DOMリスナーの登録はcompile関数内では無く、リンク関数内で行うべきです。
  2. transclude関数をcompile関数に渡すことは、例えば外部スコープを正しく理解できないため非推奨です。(翻訳に自信なし) 代わりに、transclude関数はlink関数に渡して使用するようにしてください。 via. jsStudio

となっておりますが、後者はあんまりよく分かりません。(要は tranclude 関数を compile 内で使うなっていうのは分かるけど。transclude に渡す $scope が不定だからってこと?)

  • 引数

    • element: テンプレートをコンパイルした結果の jqLite オブジェクト
    • attrs: ディレクティブに指定されたアトリビュートを正規化したオブジェクト
    • transcludeFn: ディレクティブ要素以下に存在する内容の要素を取得するための関数

      compile と link で同じようなものがあるが、タイミングの違いからか tranclude の処理自体も変わっている

      // example 
      transclude(
          // transclude 内に適用するスコープのオブジェクトを渡す
          $scope,
          // コンパイルされた transclude オブジェクトの要素とスコープを受け取る
          function($element, $scope) {
              // transclude をコンパイルした結果で何かを行いたければここでやる
          },
          // これだけなんだかよく分からない。 { '{controller_name}': {ControllerInstance}} みたいに渡すっぽい
          // 処理見てると transclude の $element に $element.data(name + 'Controller', instance) とか紐づけてるけど何用なのかは不明
          {});
      
  • 戻り値の設定値

    • オブジェクトの場合

      • pre: 正式には preLink 。コンパイル後、かつリンクの直前に呼ばれる
      • post: 正式には postLink 。コンパイル後、かつリンクの完了後に呼ばれる( link と同様のタイミング)

      いずれも引数は scope, element, attrs, controller, transcludeFn ( link と一緒)

    • 関数の場合
      上記の postLink が使われる

link

リンクの段階は、コンパイル段階が終了し、DOM やモデルに対してリスナーが設定される段階。

compile と違い、link は全てのディレクティブインスタンスに対して走るので注意すること。主に、それぞれのディレクティブに対して別々に設定しなければならないものがあれば、link に渡す関数の中で行う。

コンパイル段階では取得できなかった $scope が取得できる。

compile と同時に指定することはできず、指定した場合は compile の方で指定した関数が呼ばれる。

  • 引数

    • scope: このディレクティブのスコープ
    • element: ディレクティブが使用される要素の jqLite インスタンス
    • attrs: ディレクティブに指定されたアトリビュートを正規化したオブジェクト
    • controller: ディレクティブに指定された controller のインスタンス
    • transcludeFn: ディレクティブ指定以下に存在する内容の要素を取得するための関数

      compile と link で同じようなものがあるが(ry
      取得の仕方は、compile のほうと変わらないが、こちらは第3引数を渡すことができなくなっている。


compile と link が難しいかつユースケースがいっぱいあってイマイチ掴めてないので追記予定です。っていうか誰か教えてください。

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
59