金井 健一

AngularJSのControllerとScopeの基礎を学ぼう

連載企画「AngularJS徹底解説」の第3回目は、ControllerとScopeの基礎について解説していきます。

AngularJS は MVW(hatever)!!!

AngularJSはMVC(Model-View-Controller)フレームワークと呼ばれることが多いですが、一部の開発者からは MVVM(Model-View-ViewModel)である、という声もあがっていて、ある時期に「一体どっちなんだ!?」という状態になりました。
そこで、そういった経緯に対してAngularJSチームが、そこについての議論は本筋ではないとして、AngularJSはMVW(Model-View-Whatever)である、と明言しています。

今回解説するControllerは、このWhateverの役割にあたる機能です。実際に「Whatever = 何でもいい」とはいっても、”Controller”という名のとおり、MVCパターンとして見れば M-V-“Controller”にあたる機能ですので、その概念から外れることはありません。

Controller の基本

コントローラは以下のように記述します。他にもいくつか書き方があるのですが、今回はこの書き方で解説していきます。

まず、myAppアプリにmyCtrlというコントローラを作成してみます。

以下の意味になります。

$scopeについては、 ビューにデータなどを渡したり、ビューから発生したイベントを監視するなど、ビューとのやり取りを行うことができる特別なオブジェクトです。(AngularJSでは直接DOM操作をすることは推奨されていないため、この$scopeを通じて行います。そのためほとんどのケースでコントローラを記述する際に定義することになります)

また複数依存するサービスが複数ある場合は、

このように、配列に追加していく形で定義することができます。

先ほどの$scopeを利用して、ビューにモデルの値を引き渡します。
テンプレート(HTML)箇所については、前回までに解説した通りです。

$socpeを利用することで、ビューとやりとりすることができます。①で様々な形式のモデルの初期化、②で$scopeにプロパティを追加していくかたちで、 ビューに値を渡すことができます。
上記のほかにも複雑なオブジェクトや、関数などもバインドすることが可能です。

sample1

Controller の適用範囲

コントローラに限らず、ではあるのですが、AngularJSのディレクティブには適用範囲があります。
それは、そのディレクティブを定義したDOMの範囲とイコールです。

①のようにng-controller="myCtrl"と定義したdivタグの内部のみ、初期化されたnameを表示(取り扱う)することができます。 ②はng-controller="myCtrl"が定義されているdivタグの外側にあるため、myCtrlで定義したnameにアクセスすることはできません。

このように、AngularJSではそれぞれの コントローラなどのディレクティブがお互いに干渉しないよう、適用される範囲のみでそれぞれの機能を果たします。そうすることで個々の機能が疎結合になり、保守性が高くなります。

Controller の分割

最初に挙げたサンプルのように画面全体に対して1つのControllerで運用していった場合、かなり多機能なコントローラになってしまいます。

例えば以下のケースだとどうなっているか見ていきましょう。

上記はbody全体に対してのみのコントローラとなっています。ヘッダやフッタに関しては、一般的なアプリケーションでは共通的な機能と言えるでしょう。

それに対して上記の場合は、画面ごとにヘッダやフッタのデータやイベント等の振る舞いを毎回記述しなくてはなりません。先述したとおり、保守性を高めるために、 コントローラを分割すると良いでしょう。

上記のようにコントローラを分割することで、機能の塊を小さくできます。headerCtrlfooterCtrlを切り分けることによって、アプリケーション全体で共通的なヘッダ、フッタの機能として利用することもできます。

また、メインコンテンツ内も機能的に煩雑になっているようであれば、さらにコントローラを分割して運用していくとよいでしょう。

Scope

スコープはビューとコントローラの間に立って、モデルをビューにバインディングしたり、ビューから発生したイベントを受け取って何かしら振る舞う、といった役割を持ちます。$scopeはそれを実際に行なうためのオブジェクトです。

スコープはDOMツリーと同様ツリー構造になっていて、ng-appを基点にDOMツリーに沿うように構成されます。そしてng-controller と記述している箇所でng-appの子スコープとして新たなスコープが生成されています。Chrome DevToolsなどで該当するDOMを見てみると、ng-scopeというクラス名が自動的に付与されているため、これを元に確認することができます。

最初のサンプルの場合、ng-repeatでも新しくスコープを生成するため、expertList<li>にも同じように付与されています。

sample1-a

“Controllerの適用範囲” で解説した内容はこのスコープのツリー構造と関わっています。

以下の例を見てみましょう。

実行してみると、このように表示されます。

scope

スコープはツリー上に構成されるため、スコープの親子関係が生まれます。
原則、子スコープから親スコープの値は参照できますが、その反対はできません。
親であるmyCtrlで定義された、nameというプロパティは、子であるmyChildCtrlnameとして参照することができるため、表示されています。
反対に、親スコープから子スコープのプロパティを参照できないため、②では何も表示されていません。

子スコープが新たに生成されるタイミングは、コントローラを定義する(ng-controller)など、一部の(ビルトイン)ディレクティブ などを定義した場合です。
どのディレクティブ が新しくスコープを生成するかや、詳細な挙動については、AngularJS にかなり慣れている必要があります。
しかし、はじめから完璧に理解する必要は全くありません。スコープという概念があり有効範囲や親子関係がある、ということを頭の片隅においておくと良いでしょう。

Controller 間でデータ共有

親子コントローラ間でデータを共有するためには以下のように記述しましょう。

これまでのサンプルとの違いは、$scope.autor.nameのように、直接$scope.nameとしていない点です。
このように$scopeとバインドしたいプロパティとの間に、オブジェクトを挟むことで、親子間でデータを共有することができます。

こうすることで、親子スコープのどちらのinputでも、片方の入力値を書き換えれば、もう一方のデータも書き変わります。
先述してきたサンプルの場合は、JavaScriptのプロトタイプチェーンの都合上、親子関係が破綻してしまいます。そのため、値を書き換えても相互にデータの書き換えを行なうことができません。
データの書き換えや参照が思ったようにうまくいかない場合は、このことを覚えておくとよいかもしれません。

まとめ

今回は、ControllerとScopeの基礎を解説しました。
特にスコープについては複雑な概念や実装になっていますので、いきなり全てを把握することは難しいです。AngularJS の経験を積むとともに徐々に学んでいきましょう。
次回はサービスについての解説を予定しています。

Powered byNTT Communications

tag list

アクセシビリティ イベント エンタープライズ デザイン ハイブリッド パフォーマンス ブラウザ プログラミング マークアップ モバイル 海外 高速化 Angular2 AngularJS Canvas Chrome Cordova CSS de:code ECMAScript Edge Firefox Google Google I/O 2014 HTML5 Conference 2013 html5j IoT JavaScript Microsoft Node.js PhoneGap Polymer React Safari SkyWay TypeScript UI UX W3C W3C仕様 Webアプリ Web Components WebGL WebRTC WebSocket