はじめに
先日AngularJsを初めて勉強したのですが、これまでモダンなフレームワークを用いたフロントエンドの改修経験が全くない自分にとって躓くポイントが多々ありました。
躓いた点の中で、個人的に重要だと思ったコントローラーの定義方法について解説します。
バージョンについて
本記事で扱うフレームワークはAngular(ver2以降)ではなく、AngularJs(ver1.x系)です
初めてコントローラーの定義を見た時の感想
タイトルにもある通り、AngularJsのコントローラーの定義の理解に時間がかかりました。
AngularJsを解説しているサイトを色々見ていると、サンプルコードとして以下のようなソースが良く掲載されています。
See the Pen Untitled by takashi.ebina (@takashi-ebina) on CodePen.
上記のソースを見た時に、
MainController側で定義されている$scope.message とHTMLの{{message}}の箇所がバインドされている
というのはなんとなく理解できました。
ですが、js側のコントローラーの定義に関して2つの疑問あり、腹落ちできない状態でした。
angular.module('App', [])
.controller('MainController',
['$scope', function ($scope) {
$scope.message = 'Hello world!';
// Javascriptの処理・・
}]);
-
疑問点1
- 配列内の文字列とfunctionの引数には何を指定したらよい・・?
-
['$scope', function ($scope) {...の箇所
-
- 配列内の文字列とfunctionの引数には何を指定したらよい・・?
-
疑問点2
- 配列の中に文字列とfunctionを突っ込んでいるけどなんでこんな書き方しないといけない・・?
- controllerメソッドの引数に配列を指定して、その配列の中には文字列とfunctionが存在するという構造がとても読みづらく、何をしたいのかよくわからないなというのが率直な感想。
- 配列内の文字列とfunctionの引数名がどちらも
$scopeとなっているが、この二つに関連性があるのか?
- 配列の中に文字列とfunctionを突っ込んでいるけどなんでこんな書き方しないといけない・・?
特に2つ目の疑問点に関しては、フレームワークなのでそういうものとして覚えるしかないのかなと思っていました。
ですが、AngularJsについて理解を深めるにつれて上記の記法に意味があることがわかりました。
コントローラーの定義について
疑問点1:配列内の文字列とfunctionの引数について
['$scope', function ($scope) {...の実態を理解するためには、AngularJsのサービスという概念について知る必要があります。
サービスについて
AngularJsが指すサービスはアプリ上で特定のタスクを実行するためのコンポーネントのことです。
(個人的にはAngularJsが用意している便利な機能をサービスという形で提供していると理解しています。)
一例ですが、サーバとの非同期通信を行うような機能は$httpサービスという形で提供されています。
サービスの利用方法について
では、サービスという便利な機能が利用できるようになるためにはどうすれば良いか?
先ほど取り上げた$httpサービスを利用するためにはコントローラーの定義を以下の通りに修正します。
angular.module('App', [])
.controller('MainController',
- ['$scope', function ($scope) {
+ ['$scope', '$http', function ($scope, $http) {
$scope.message = 'Hello world!';
// Javascriptの処理・・
}]);
配列の文字列、functionの引数名にサービス名を追加することでサービスが利用できます。
今回の場合は$scope、$httpサービスが利用可能です。
(この書き方そのもの説明に関しては、疑問点2のタイミングで解説いたします。)
また、今回の記事の本題であるコントローラーの定義から逸れますが、$httpサービスの使い方についても紹介します。
$httpサービスの使い方
$httpサービスはサーバとの非同期通信を行うサービスですが、今回はRESAS-APIを用いて都道府県データを取得する画面を作成いたします。
サンプルソース
angular.module('App', [])
.controller('MainController',
['$scope', '$http', function ($scope, $http) {
/* 【処理1】
* GETで都道府県情報を取得する
* 設定内容はRESAS-API仕様書参照
* URL:https://opendata.resas-portal.go.jp/docs/api/v1/prefectures.html
*/
$http({
method: 'GET',
url: 'https://opendata.resas-portal.go.jp/api/v1/prefectures',
dataType: 'jsonp',
jsonpCallback: 'callback',
headers: {
'X-API-KEY': 'APIキー' // 自身で発行したAPIキーを指定してください
}
}).
/* 【処理2】
* 非同期通信の結果で呼ばれるメソッド決まる
* 正常の場合:successメソッド内のコールバック関数
* エラーの場合:errorメソッド内のコールバック関数
*/
success(function (data, status, headers, config) {
// 非同期通信成功時に呼ばれる処理
$scope.responseData = data;
}).
error(function (data, status, headers, config) {
// 非同期通信失敗時に呼ばれる処理
console.log(status);
});
}]);
<!DOCTYPE html>
<html lang="ja" ng-app="App">
<head>
<title>AngularJS 入門</title>
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta charset="UTF-8">
<script src="//code.angularjs.org/1.5.0/angular.min.js"></script>
<script src="./app.js"></script>
</head>
<body>
<h1>RESAS-API 実行結果</h1>
<div ng-controller="MainController">
<div>{{responseData}}</div>
</div>
</body>
実行結果
サンプルソースの解説
処理1について
GETでHTTP通信を行い、都道府県の情報を取得しています。
都道府県の情報を取得するためのURLやパラメータなどは、API仕様書を参考にしてください。
(APIキーが必要ですが、別途発行してしてください。)
処理2について
【処理1】の非同期通信の結果で呼ばれるメソッドが決まります。
- 正常の場合:successメソッド内のコールバック関数
- エラーの場合:errorメソッド内のコールバック関数
このコールバック関数に渡される引数の値は以下となります。
| 引数名 | 概要 |
|---|---|
| data | レスポンスボディ |
| status | レスポンスのHTTPステータスコード |
| headers | ヘッダーを取得する関数 |
| config | リクエストを作成するのに利用された構成オブジェクト |
今回の場合は、レスポンスボディの情報を$scopeオブジェクトに設定して画面に出力しています。
疑問点2:配列の中に文字列とfunctionを指定する書き方について
この独特な書き方を理解するためには、AngularJsのDI(依存性の注入)1 について理解する必要があります。
DI(依存性の注入)について
AngularJsでは、引数の変数名からDIすべきオブジェクトを判断することが可能 であり、以下でサービスを利用できます。
angular.module('App', [])
.controller('MainController', function ($scope) {
$scope.message = 'Hello world!';
});
疑問点1でサービスを利用するために、functionの引数名にサービス名を記載すると説明しました。
DIの観点で言い換えると次の通りに説明できます。
- コントローラーの処理実行時にAngularJsがfunctionの引数名から必要なオブジェクトを判断する
- 今回は
$scopeサービス
- 今回は
- 引数に渡すことでコントローラの処理内でサービスが利用できる
また、引数の変数名からDIすべきオブジェクトを判断するという仕組み上、「$scope」から「scope」(存在しないサービス名)に変更するとエラーが発生します。
angular.module('App', [])
.controller('MainController',
function (scope) { // 存在しないサービス名
$scope.message = 'Hello world!';
});
angular.js:13236 Error: [$injector:unpr] http://errors.angularjs.org/1.5.0/$injector/unpr?p0=scopeProvider%20%3C-%20scope%20%3C-%20MainController
at angular.js:38:1
at angular.js:4397:19
at Object.d [as get] (angular.js:4550:39)
at angular.js:4402:28
at d (angular.js:4550:39)
at e (angular.js:4574:58)
at Object.invoke (angular.js:4596:18)
at T.instance (angular.js:9855:24)
at u (angular.js:8927:34)
at g (angular.js:8226:13)
(anonymous) @ angular.js:13236
コードのminifyを行う場合のDIについて
先ほどのfunctionの引数名とサービスの名前を一致させるというやり方の場合、コードのminify2を行う際に、DIが失敗しエラーとなります。
これは、minifyを行った際にfunctionの引数名が短縮されてしまい、AngularJs側でDIすべきオブジェクトを特定することができなくなっているためです。
// 引数名が「$scope」→「a」に変更されているが、「a」という名前のサービスが存在しないためエラーとなる
angular.module("App",[]).controller("MainController",function(a){$scope.message="Hello world!"});
コードのminifyすると動作ができないといったケースでは、別の実装をする必要があります。
その実装が配列アノテーションという手法であり、本記事で最初に取り上げたソースコードとなります。
配列アノテーションの書き方
['サービス名1','サービス名2', ・・・, function (変数名1, 変数名2, ・・・) {・・・ コントローラーの処理 ・・・}]
angular.module('App', [])
.controller('MainController',
['$scope', function ($scope) {
$scope.message = 'Hello world!';
// Javascriptの処理・・
}]);
上記のような配列アノテーションを用いてコントローラーの宣言をした場合、サービス名が文字列リテラルとして指定されるため、minifyの対象となりません。
angular.module("App",[]).controller("MainController",["$scope",function(a){a.message="Hello world!"}]);
minifyした場合、配列の一つ目の要素とfunctionの第一引数の名前が一致しません。
一方で配列アノテーションでは、引数の並び順と、対応関係のある引数にDIを行うため、名前が一致しても問題ありません。
おわりに
AngularJsのコントローラーの説明は以上となります。
コントローラーの実装はあまり見慣れない書き方で、個人的には難しかったのですが少しでも参考になる方がいましたら幸いです。
参考サイト
- AngularJSのControllerとScopeの基礎を学ぼう
- js STUDIO
- AngularJSのDIの仕組み、minify対策は覚えておこう!
- AngularJSの「サービス」で理解するDI(Dependency Injection:依存性注入)の基本
-
コード上の不要な改行やインデントを削除することでファイルサイズを圧縮して読み込みの速度を向上させることです。
参考URL:Minify(ミニファイ)とは何か?圧縮方法とJS・CSSの軽量化のメリットを解説 ↩
