はじめに
先日、AngularJsを初めて勉強したのですが、
これまでFWを用いたフロントエンドの改修経験が全くなく、
勉強の過程で色々つまづいたので個人的に重要そうなところを備忘録として残します。
今回は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の引数について
controllerメソッドの引数で渡している文字列およびfunctionに指定している引数の実態を理解するためには、
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のコントローラーの説明は以上となります。
コントローラーの実装はあまり見慣れない書き方で、個人的には難しかったのですが少しでも参考になる方がいましたら幸いです。
参考
-
DIについての説明は以下のサイトがわかりやすいです。
参考URL:https://qiita.com/okazuki/items/a0f2fb0a63ca88340ff6 ↩ -
コード上の不要な改行やインデントを削除することでファイルサイズを圧縮して読み込みの速度を向上させることです。
参考URL:https://ssaits.jp/promapedia/technology/minify.html ↩