AngularjsでDirectiveを作るときに考えていること

最近、仕事や趣味でAngularjsのDirectiveを作る機会が何回かありました。Directiveは非常に強力な機能ですが、使い方を誤ると碌なことにならないので現在どのような方針でDirectiveを作っているのかを書きます。

いまや最も優れたJavaScriptフレームワーク「AngularJSリファレンス」出版記念会 イベントレポートでも触れられているように、ベストプラクティスが見つかっていない感じなので色々な人の意見を聞きたいです。

基本的な方針

可能な限りDirectiveは作らない

DirectiveはAngularjsの中でも仕様が複雑で理解しづらく、ファイルが分散するので作るだけの明確な理由がないものについてはDirectiveにするべきではない。Angularjsはビルトインディレクティブが充実しており、大半の問題はこれを利用すれば解決する。無駄な重荷を負う必要はない。

どのような時にDirectiveを作るか

以下の様な場合にDirectiveを作る

  • この実装をDirectiveにせずに放置した場合
    • HTMLが異常に長くなったりネストが深くなったりして、人間が理解できるものではなくなる
    • Controllerのscopeに大量のメソッドが生えることになる
    • 異なる場所で大量のHTMLを何度も書くハメになる

restrict: 'EA' は使わない

作ろうとしているDirectiveが属性的(Attribute)なものなのか、要素的(Element)なものなのかをよく考えて決める。 公式に決まりがあるわけではないが、必ずどちらかに決めて restrict を指定する。

  • Attribute は以下のいずれかもしくは全てに当てはまる場合に用いる
    • テンプレートを展開しない
    • 対象とするDOMに対して何かしらの入力(テキストボックスに値を入れるとか、クリックするとか、ドラッグするとか)を実装することが主な責務である
  • Element は以下のいずれかもしくは全てに当てはまる場合に用いる
    • テンプレートを展開する
    • 対象とするDOMを利用して何かしらの出力(表示とか)を実装することが主な責務である
Attribute Directiveはまだあんまり整理できていない

以下、Attributeとして作成するDirectiveをAttribute Directive、Elementとして作成するDirectiveをElement Directiveと表記する。

Directiveを設計する

BEMで考える

BEMという方法論がある。これ自体はCSSの命名ルール等でよく利用されるが、AngularjsのDirectiveをどう分割するか、どのように設計するかを考えるときのヒントとしてBEMを用いる。

BlockとElement Directiveを対応させる

BlockもしくはModifierの単位でDirectiveを分割する。Blockが入れ子になっている場合は最も外側のBlockから以下の問答をして'Yes'が多ければ子供に分割していく

  • 自分の子Blockを再利用して他のところで使いたいか
  • 自分の子Blockを「もう一度書け」と言われた時に、コードを見ないで書くことは不可能か
  • ユーザがこのアプリケーションを使った時、子Blockに求める機能と親Blockに求める機能が全く別か
Attribute Directive に関しては知見がなくて何も言えない…。

ElementとjqLiteで操作する範囲を対応させる

linkオプションの中でDOM Elementを操作したいケースが存在するかもしれない。それは問題ない。 ただしlinkオプションで提供されるjqLiteオブジェクトのうち、操作して良いのはElementに当たる部分のみである。その範囲を超えてDOMを操作してはいけない。

ModifierとIsolate Scopeを対応させる

原則としてIsolate Scopeを利用する。外部から渡されるオブジェクトは以下のものに分類する。

  • データ
  • Blockの状態(≒ Modifier)

また、以下の様な原則を守る。

  • データとBlockの状態が混在したオブジェクトを渡さない
  • Blockの状態を単一で巨大なオブジェクトとして渡さない

【補足】BEMについて

BEMについての解説はここを参照する。

Directiveを書く

コーディングに関するルールを列挙する。これは正しいかどうか確信がないので信用しすぎないこと。

  • タグ名は '-' 区切りで記述する
  • アプリケーション固有のDirectiveの場合は固有の接頭詞をつける
  • Element DirectiveのテンプレートはtemplateUriを利用する
    • ただしアプリケーションのユースケースを考慮し、リクエストの増大と可読性の高さを天秤にかけること
  • Isolate Scopeのプロパティ=&@はそれぞれの役割を理解した上で利用する
    • 雑に考えると以下の様な分類になる (引用)
      • @ Text Binding
      • & One-way Binding
      • = Two-way Binding
  • 原則としてIsolate Scopeを利用する(親Scopeを継承しない)