Monaca入門:Onsen UI+AngularJSで作るハイブリッドモバイルアプリ(3)
Onsen UIの舞台裏で働くAngularJSの世界
AngularJS流のデータ/コントローラー/表示の実装方法と、AngularJSのディレクティブによるHTML要素の操作方法、データの追加、AngularJS機能のサービスについて解説する。
前回は、Monacaで作成した“初めてのOnsen UIアプリ”のファイルの中身を調べ、それをAngularJS流に書き換えた。3回目の今回は、Onsen UIの舞台裏で働くAngularJSの世界に入っていく。
AngularJSはグーグル純正のJavaScriptフレームワークで、モデル、ビュー、コントローラー(MVC)というデザインパターンが採用されている。連載1回目で述べたように、Onsen UIを使ったMonacaアプリでJavaScriptをAngularJS流に記述できるようになると、AngularJSを直接扱わない標準的な方法よりも、アプリの“かゆいところに手が届く”ようになる。
新しいことを覚えるときには多少の苦痛を伴うかもしれないが、AngularJSの定番的な書き方を一度覚えると、あとはそれをどのアプリにも応用できるようになる。またプログラムを通常のJavaScriptで記述するときには多くのコードが散在しがちになるが、AngularJSではコードをモデルとビュー、コントローラーに分けて記述するので、その心配がなくなる。
MonacaアプリでのAngularJS流データ/コントローラー/表示
まずはAngularJSのMVCをMonacaアプリではどう捉えればよいかについて見ていこう。ウィキペディアの「Model View Controller」ページには、モデル、ビュー、コントローラーは次のように書かれている。
- model: アプリケーションデータ、ビジネスルール、ロジック、関数
- view: グラフや図などの任意の情報表現
- controller: 入力を受け取り、modelとviewへの命令に変換する
Monacaアプリでは、モデルはJavaScriptで扱えるアプリのデータと考えられる。データはアプリ内部に持つことも、インターネット経由でサーバーから得ることもできる。
コントローラーは簡単にいうとJavaScriptの関数だ。書き方や使い方はAngularJSで決められているので、それに従えばよい。AngularJSのコントローラーはデータを取り込んでプロパティとして設定し、その変化を監視する。
ビューは、コントローラーがデータを料理した後のHTMLでの表示結果と見なすことができる。コントローラーのプロパティが変化したら、その結果はアプリ画面に自動的に反映される。
このMonacaアプリでのAngularJS流のデータ/コントローラー/表示を図で表すと図3-1のようになる。
ただしAngularJSの場合、コントローラーと表示の間にスコープというものがある。スコープはコントローラーと表示の間を取り持つ仲介者だ。コントローラーは表示の存在を知らず、表示にはスコープを通してコントローラーのプロパティの変化が届く。
ではこのデータ、コントローラー、表示と呼んでいるものがどういうものなのか、実際にアプリを作成しながら具体的に見ていこう。なお今回の説明は、あくまでもOnsen UIを使ったMonacaアプリでAngularJS流の書き方をできるだけ容易に理解できるようにするためのものであり、AngularJSそのものについてではない。AngularJSそのものを理解したい方は本家Webページや、専門に書かれた書籍(例えば『AngularJSリファレンス』)など、別のリソースに当たってほしい。
「Onsen UI最小限のテンプレート」で使用できるAngularJS流の基本的なindex.html
Monaca IDEにログインし、ダッシュボードの[プロジェクトの作成]ボタンをクリックする。すると[プロジェクトの作成]ウィンドウが現れるので、「Onsen UI最小限のテンプレート」を選択してプロジェクトを作成する。
AngularJS流の書き方をするには、前回行ったように、プロジェクトのindex.htmlファイルを修正する必要があるが、以下に「Onsen UI最小限のテンプレート」から作成されるindex.htmlファイルをAngularJS流に置き換える基本的なコードを用意した。これをコピーし、作成した新しいプロジェクトのindex.htmlファイルの中身と置き換える。このindex.htmlでは、angular.module()
が返すmyApp
モジュールを変数app
に割り当て、後で利用できるようにしている。変数app
は、AngularJSの機能を持つ、このアプリそのものだ。
<!DOCTYPE HTML>
<!--AngularJSアプリとしての範囲を指定する-->
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, maximum-scale=1, user-scalable=no">
<script src="components/loader.js"></script>
<link rel="stylesheet" href="components/loader.css">
<link rel="stylesheet" href="css/style.css">
<script>
// Onsen UIモジュールを注入したmyAppモジュールを作成し、変数appに代入する。
var app = angular.module('myApp', ['onsen']);
</script>
</head>
<body>
<ons-navigator page="page1.html"></ons-navigator>
</body>
</html>
|
データ
データはJavaScriptで扱えるJavaScriptのオブジェクトなので、難しく考える必要はない。次のデータは、id
とcolor
というプロパティとその値を持つ、3つのオブジェクトを入れた配列だ。アプリではこれをデータとして使用する。
[ {"id":"1", "color":"lightgrey"}, {"id":"2", "color":"red"},{"id":"3", "color":"green"}]
|
コントローラー&表示
コントローラーは、AngularJSで定められているJavaScriptの関数なので、決まりに従って記述して使用する以外ない。
まずHTMLで、AngularJSのng-controller
属性を使って、次のようにコントローラーの名前(MainController)を指定する。このMainController
という名前のコントローラーは、この<div>
と</div>
で囲まれた範囲を自分の守備範囲とする。この守備範囲が前述したスコープに当たる。<p>
タグ内の{{color}}
はAngular式と呼ばれるもので、実際に表示されるときにはMainController
のスコープのcolor
プロパティの値に置き換えられる。またアプリの実行時、color
プロパティの値に変化があると、その値に自動的に更新される。
<div ng-controller="MainController">
<p>{{color}}</p>
</div>
|
次にJavaScriptでは、MainController
と名付けたコントローラーを作成する。コントローラーは、AngularJSの機能を持つ、このアプリそのものを表す変数app
からcontroller()
関数を呼び出して作成する。controller()
には次のように、コントローラーの名前とコントローラーの動作を定める関数を渡す。この関数は引数として、コントローラーの「守備範囲」を意味する「スコープ($scope)」を受け取る。コントローラーのプロパティは、コントローラーと表示の仲介者である$scope
のプロパティとして定義する。ここでアプリのデータを割り当てると、このコントローラーにデータを持たせ、その変化を監視させることが可能になる。
app.controller('MainController', function($scope){
$scope.colorData = [ {"id":"1", "color":"lightgrey"}, {"id":"2", "color":"red"},{"id":"3", "color":"green"}];
$scope.color = $scope.colorData[0].color;
});
|
コントローラーの作成と、スコープへのデータの割り当て
ここまでをまとめると、index.htmlファイルのJavaScriptコードは次のようになる。
// Onsen UIモジュールを注入したmyAppモジュールを作成し、変数appに代入する。
var app = angular.module('myApp', ['onsen']);
// MainControllerを作成
app.controller('MainController', function($scope){
// コントローラーの守備範囲(スコープ)の中にあるデータ
$scope.colorData = [ {"id":"1", "color":"lightgrey"}, {"id":"2", "color":"red"},{"id":"3", "color":"green"}];
// スコープのcolorプロパティにデータの値を割り当てる
$scope.color = $scope.colorData[0].color; // lightgrey
});
|
コントローラー名と監視対象のプロパティの指定
index.htmlファイルに記載されていた<ons-navigator page="page1.html">
によって表示されるpage1.htmlファイルのコードは次のようになる。
<ons-page>
<ons-toolbar>
<div class="center">一番基本的なAngularJSアプリ</div>
</ons-toolbar>
<!-- ng-controllerを使ってコントローラーの名前を指定-->
<!--MainControllerの守備範囲はこのdiv内-->
<div ng-controller="MainController">
<!--監視するのはMainControllerのcolorプロパティ-->
<p>{{color}}</p>
</div>
</ons-page>
|
AngularJS流データ/コントローラー/表示を実装したMonacaアプリの実行
ここまでの作業を保存し、Monacaデバッガーで実行すると、図3-2の結果が得られる。「lightgrey」という文字は<p>{{color}}</p>
がAngularJSによって処理された結果で、元の値はデータの{"id":"1", "color":"lightgrey"}
オブジェクトにある。
応用編:colorプロパティを変化させる
次は、スコープのcolorプロパティをデータの別の値に変更して、表示がどう変化するかを見てみよう。
「lightgrey」という色名のテキストを変化させる
HTMLでは次のように、コントローラーの<div>
要素の中に、Onsen UIのボタンの<ons-button>
を2つ追加する。ng-click
は通常のJavaScriptのonclick
に当たる<ons-button>
の属性で、実行する関数への呼び出しも通常と同じように""
で囲んで指定する。
<!-- <div ng-controller="MainController">の中-->
<p>{{color}}</p>
<ons-button ng-click="changeRed()">赤に変更</ons-button>
<ons-button ng-click="changeGreen()">緑に変更</ons-button>
|
JavaScriptコードではapp.controller()
関数内で、次のように<ons-button>
のクリックにより呼び出される関数を、スコープの関数として定義する。この関数では、コントローラーと表示の仲介者であるスコープのcolorプロパティに、データの適切な値を割り当てる。
// app.controller()の関数内の一番下に以下を追記
// スコープの関数を定義
$scope.changeRed = function(){
$scope.color = $scope.colorData[1].color; // red
}
$scope.changeGreen = function(){
$scope.color = $scope.colorData[2].color; // green
}
|
ここまでの変更を保存し、デバッガーでアプリを実行する。[赤に変更]ボタンをタップすると、<p>
要素のテキストが「red」に変わり、[緑に変更]ボタンをタップすると「green」に変わる。これはpage1.htmlファイルに記述した<p>{{color}}</p>
がダイナミックに<p>red</p>
や<p>green</p>
に変化した結果を示している。
ページ上にある領域の配色を変化させる
このcolorプロパティの変更はそのまま、実際のカラーの変更に適用できる。次はボタンのタップで<div>
要素のカラーを変更してみよう。
HTMLのコントローラーの<div>
要素の中に、次の<div>
を追加する。style
属性のbackground-color
プロパティには{{color}}
というAngular式を割り当てる。
<!-- <div ng-controller="MainController">の中の一番下に追記-->
<div class="box" style="background-color:{{color}};"></div>
|
アプリのCSSコードは、[www]-[css]フォルダー内にあるstyle.cssファイルに記述する。IDEのプロジェクトパネルで[style.css]をダブルクリックしてコードエディターに開き、次のboxクラスを記述する。
.box { width : 300px; height : 100px; margin-top : 50px; }
そしてJavaScriptは、実は何も変更する必要はない。カラーを変化させる<div>
がコントローラーの守備範囲にある限り、その<div>
のスタイルにも<p>
要素と同じcolorプロパティの値が割り当てられる。図3-4は[赤に変更]ボタンのタップによって、<div>
が赤に変わったところを示している。
■
以上がOnsen UIに取り入れられているAngularJS流の具体的なデータ/コントローラー/表示の簡単な例だ。読んですぐ理解するのは難しいかもしれないが、{{color}}
のダイナミックな変化には驚かれたのではないだろうか。通常、JavaScriptでは、イベントリスナーを使って変化を監視するが、AngularJSではそれが自動化される。これはAngularJSの大きな特徴の1つだ。
もちろんMonacaアプリの作成に生かせるAngularJSの機能はこれだけではない。続いてはディレクティブと呼ばれる強力な機能を見ていこう。
HTML要素を操作するための、AngularJSのディレクティブ
AngularJSには、コントローラーはスコープのプロパティを変更するだけで、HTML要素の実際の操作はディレクティブで行う、という流儀がある。ディレクティブは、HTML要素や要素の属性などで使用できるAngularJSの強力な機能で、これまで使ってきたons-
で始まるタグ(<ons-page>
など)やng-
で始まる属性(ng-click
など)もこのディレクティブだ。AngularJSやOnsen UIで定義されているディレクティブをタグや要素の属性として使うと、特別な機能を持つ要素に早変わりする。
既製のディレクティブ
ディレクティブの強力な機能を示す端的な例にng-repeat
ディレクティブがある。これは前述の$scope.colorData
の場合、次のように使用できる。
<!-- <div ng-controller="MainController">の中-->
<ul>
<li ng-repeat="data in colorData">
{{data.id}}:{{data.color}}
</li>
</ul>
|
ng-repeat
はここでは<li>
要素の属性で、値として"data in colorData"
を指定している。これにより$scope.colorData
配列に含まれるデータの各オブジェクトにdata
でアクセスできる(JavaScriptのfor in
ループに似ている)。{{data.id}}
によってオブジェクトのid
プロパティが書き出され、{{data.color}}
でcolor
プロパティの値が書き出される。colorData
には今、3つのオブジェクトが入っているので、{{data.id}}:{{data.color}}
は3回繰り返される。
図3-5はこの実行結果で、<li>
要素の中には「1:lightgrey」と「2:red」、「3:green」というテキストが入っている。
ng-repeat
はAngularJS既定のディレクティブだが、ディレクティブは自作することもできる。
ディレクティブの自作
そのためには、myApp
モジュール(=変数app
)のdirective()
関数にディレクティブ名と関数を渡して、myApp
モジュールに自作のディレクティブを登録する。
次のJavaScriptコードは、要素の背景色をスコープのcolor
プロパティに一致させるcolorBox
ディレクティブの作成例だ。このコードはこれまでと異なり、app.controller('MainController', function($scope){...});
の下に記述する。
// app.controller();の終わり
});
app.directive('colorBox', function(){
return {
restrict:'A',
link:function(scope, element){
scope.$watch('color', function(){
// elementはjqLightオブジェクト
element.css('background-color', scope.color);
// element[0]は素のHTML要素
// element[0].style.backgroundColor = scope.color;
});
}
}
});
|
directive()
関数に渡す関数は少し分かりづらいが、この関数はオブジェクトを返している。そのオブジェクトではrestrict
プロパティとlink
プロパティを定義しており、restrict
プロパティには「A」を指定している。Aはこのディレクティブが属性(attribute)として機能することを意味する。link
プロパティには関数を割り当てている。この関数には引数としてディレクティブを使用する要素(element)とスコープ(scope)が渡される。ディレクティブで行いたいことはこの関数に記述する。
渡されたスコープの$watch()
関数を使うと、そのスコープのプロパティの変化が監視できる。ここではcolorプロパティを監視し、変化があった場合にこのディレクティブを使用する要素の背景色をcolor
プロパティの値に設定している。引数として渡されるelement
はjqLight(=jQueryの軽量版)なので、jQueryのcss()
関数が使用できる。素のHTML要素はelement[0]
で参照できる。
自作ディレクティブの使用
colorBox
ディレクティブを使用するには、要素にcolor-box
属性を記述する。ディレクティブ名のcolorBox
でない点に注意が必要だ。大文字の「B」を小文字にして、その前にダッシュ(-)を挿入する。これによりこの<div>
は自動的にスコープのcolor
プロパティを監視し、変化があった場合にはその背景色をスコープのcolorプロパティの値に変更するようになる。
<div class="box" color-box></div>
|
自作ディレクティブの実行
ここまでのコードを保存し、デバッガーで実行してボタンをタップする。すると2つの<div>
要素のカラーが同じように変化することが分かる。
データの追加
アプリケーションのデータは簡単に追加できる。スコープのデータの配列に、新しいデータをオブジェクトとして追加するだけだ。
HTMLでは(もちろんコントローラーの<div>
内に)次のボタンを追加する。
<ons-button ng-click="addData()">データを追加</ons-button>
|
JavaScriptでは、ボタンから呼び出す関数をスコープの関数として定義する。次のコードでは、ボタンのタップで{"id":"4", "color":"yellow"}
というオブジェクトを追加し、確認のためにスコープのcolor
プロパティを新しいデータに設定している。
$scope.addData = function(){
$scope.colorData.push({"id":"4", "color":"yellow"});
$scope.color = $scope.colorData[3].color;
}
|
ここまでのコードを保存し、デバッガーでアプリを起動する。[データを追加]ボタンをタップすると、新しいデータが追加され、それがスコープのcolor
プロパティに設定されるので、図3-7に示すように、<p>
要素のテキストには「yellow」が表示され、2つの長方形は黄色になる。そしてリスト要素には4つめのデータとして「4: yellow」が表示される。
AngularJSの機能、サービス
最後に、Onsen UIを使ったMonacaアプリで役立つAngularJSの機能として、サービスを紹介しておこう。
アプリで使用するデータは、例えば天気予報アプリなどでは、外部のデータをインターネット経由で取得することになる。AngularJSではこの機能をサービスとして作成し、アプリで利用する。AngularJSのサービスは、インスタンスが1つしかないオブジェクトで表されるので、重複させたくないデータを扱うには最適だ。サービスはまた、複数のコントロール間で使用したいデータの共有スペースとしても利用される。
サービスの作成
ここまではデータをコントローラー内で直接作成してきたが、ここではサービスを使ってデータを扱えるようにしていこう。そのためにはmyApp
モジュール(=変数app
)のfactory()
関数にサービス名と関数を渡して、次のようにサービスを作成する。
// app.directive('colorBox', function(){...});の下
app.factory('DataService', function(){
// serviceオブジェクト
var service = {};
// serviceオブジェクトのdataプロパティにデータを割り当てる
service.data = [ {"id":"1", "color":"lightgrey"}, {"id":"2", "color":"red"},{"id":"3", "color":"green"}];
// serviceオブジェクトのgetData()関数
service.getData = function(){
// serviceオブジェクトのdataプロパティを返す
return this.data;
}
// serviceオブジェクトのaddData()関数
service.addData = function(newData){
// serviceオブジェクトのdata配列に新しいデータを追加する
this.data.push(newData);
}
// serviceオブジェクトを返す
return service;
});
|
この例では「DataService」という名前のサービスを作成している。その機能、つまりデータを参照するプロパティやデータを扱う関数は、app.factory()
に渡す関数が返すオブジェクトで定義する。これは今の場合でいうとservice
オブジェクトで、service
はデータを参照するdata
プロパティと、データを返すgetData()
関数、データを追加するaddData()
関数を持っている。
サービスの使用
このDataService
サービスをコントローラーで使用するには、app.controller()
に渡す関数の引数として、次のようにDataService
を渡す。
// サービスを利用するコントローラー
app.controller('MainController', function($scope, DataService){
……省略……
|
するとコントローラーでは、DataService
のgetData()
関数を使って、スコープのcolorData
プロパティに値を割り当てられるようになる。
$scope.colorData = DataService.getData();
|
またスコープのaddData()
関数では、DataService
サービスのaddData()
関数が次のように使用できる。
$scope.addData = function(){
DataService.addData({"id":"4", "color":"yellow"});
$scope.color = $scope.colorData[3].color;
}
|
サービスを利用するコントローラー全体は次のようになる。
app.controller('MainController', function($scope, DataService){
$scope.colorData = DataService.getData();
$scope.color = $scope.colorData[0].color;
$scope.changeRed = function(){
$scope.color = $scope.colorData[1].color;
}
$scope.changeGreen = function(){
$scope.color = $scope.colorData[2].color;
}
$scope.addData = function(){
DataService.addData({"id":"4", "color":"yellow"});
$scope.color = $scope.colorData[3].color;
}
});
|
サービスの実行
ここまでのコードを保存しデバッガーでアプリを起動して動作を確認する。動作に違いはないが、このアプリではAngularJSのサービスを作成し、それを利用している。
■
以上、今回はOnsen UIを使ったMonacaアプリでAngularJS流のJavaScriptコードを書くときに必要になるデータ、コントローラー、表示の概念から、その具体的な動作、さらにはAngularJSのディレクティブやサービスなどを見てきた。今回の目的は、最初に述べたように、MonacaアプリでのAngularJS流の書き方をできるだけ容易に理解できるようにするためであり、ここから先に進むにはやはりAngularJSそのものを理解する必要がある。これには本家Webページが最適なリソースになる。
次回は、今回得た知識を基に、オープンデータと呼ばれるインターネット上で公開されているデータをMonacaアプリで扱う方法を探っていく。
※以下では、本稿の前後を合わせて5回分(第1回~第5回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
1. Onsen UI+AngularJSで効率的にモバイルアプリが作れるMonaca
HTML+CSS+JavaScriptを使ってスマホやタブレットで動作するアプリが簡単に作成できる「Monaca」を基礎から解説する入門者向け連載(改訂版)がスタート。今回はMonaca/Onsen UI/AngularJSの概要とMonaca IDE、Monacaデバッガーを紹介。
2. Monacaで作る、初めてのOnsen UIアプリ
新規作成した“初めてのOnsen UIアプリ”プロジェクトの各ファイルをAngularJS流に書き換える。そのアプリをデバッグビルドし、デバイスに実際にインストールする。
3. 【現在、表示中】≫ Onsen UIの舞台裏で働くAngularJSの世界
AngularJS流のデータ/コントローラー/表示の実装方法と、AngularJSのディレクティブによるHTML要素の操作方法、データの追加、AngularJS機能のサービスについて解説する。
4. AngularJSの方法でMonacaアプリを作ってみよう(前編)
実践的なMonacaアプリ開発の一例として、オープンデータのWebサービスを使ったアプリの作成方法を説明する。Monacaアプリ開発実践編の第1弾。