Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
Monaca入門:Onsen UI+AngularJSで作るハイブリッドモバイルアプリ(3)

Monaca入門:Onsen UI+AngularJSで作るハイブリッドモバイルアプリ(3)

Onsen UIの舞台裏で働くAngularJSの世界

2014年11月28日 改訂 (初版:2013/11/8)

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の場合、コントローラーと表示の間にスコープというものがある。スコープはコントローラーと表示の間を取り持つ仲介者だ。コントローラーは表示の存在を知らず、表示にはスコープを通してコントローラーのプロパティの変化が届く。

図3-1 MonacaアプリでのAngularJS流データ、コントローラー、表示
図3-1 Monacaアプリでの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の機能を持つ、このアプリそのものだ。

HTML
<!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>
「Onsen UI最小限のテンプレート」から作成されるindex.htmlをAngularJS流に置き換える基本的なコード

データ

 データはJavaScriptで扱えるJavaScriptのオブジェクトなので、難しく考える必要はない。次のデータは、idcolorというプロパティとその値を持つ、3つのオブジェクトを入れた配列だ。アプリではこれをデータとして使用する。

JSON
[ {"id":"1", "color":"lightgrey"}, {"id":"2", "color":"red"},{"id":"3", "color":"green"}]
アプリで使用する配列データ(index.htmlファイル上での実装方法は後述する)

コントローラー&表示

 コントローラーは、AngularJSで定められているJavaScriptの関数なので、決まりに従って記述して使用する以外ない。

 まずHTMLで、AngularJSのng-controller属性を使って、次のようにコントローラーの名前(MainController)を指定する。このMainControllerという名前のコントローラーは、この<div></div>で囲まれた範囲を自分の守備範囲とする。この守備範囲が前述したスコープに当たる。<p>タグ内の{{color}}Angular式と呼ばれるもので、実際に表示されるときにはMainControllerのスコープのcolorプロパティの値に置き換えられる。またアプリの実行時、colorプロパティの値に変化があると、その値に自動的に更新される。

HTML
<div ng-controller="MainController">
  <p>{{color}}</p>
</div>
HTMLでコントローラーの名前を指定する(page1.htmlファイル上での実装方法は後述)

 次にJavaScriptでは、MainControllerと名付けたコントローラーを作成する。コントローラーは、AngularJSの機能を持つ、このアプリそのものを表す変数appからcontroller()関数を呼び出して作成する。controller()には次のように、コントローラーの名前とコントローラーの動作を定める関数を渡す。この関数は引数として、コントローラーの「守備範囲」を意味する「スコープ($scope)」を受け取る。コントローラーのプロパティは、コントローラーと表示の仲介者である$scopeのプロパティとして定義する。ここでアプリのデータを割り当てると、このコントローラーにデータを持たせ、その変化を監視させることが可能になる。

JavaScript
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;
});
JavaScriptでコントローラーを作成する(index.htmlファイル上での実装方法は後述)

コントローラーの作成と、スコープへのデータの割り当て

 ここまでをまとめると、index.htmlファイルのJavaScriptコードは次のようになる。

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
});
コントローラーを作成し、スコープにデータを割り当てるJavaScriptコード(index.html)

コントローラー名と監視対象のプロパティの指定

 index.htmlファイルに記載されていた<ons-navigator page="page1.html">によって表示されるpage1.htmlファイルのコードは次のようになる。

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>
コントローラー名(MainController)を指定し、colorプロパティを監視対象とするHTMLソース(page1.html)

AngularJS流データ/コントローラー/表示を実装したMonacaアプリの実行

 ここまでの作業を保存し、Monacaデバッガーで実行すると、図3-2の結果が得られる。「lightgrey」という文字は<p>{{color}}</p>がAngularJSによって処理された結果で、元の値はデータの{"id":"1", "color":"lightgrey"}オブジェクトにある。

図3-2 {{color}}にlightgreyが表示された

応用編:colorプロパティを変化させる

 次は、スコープのcolorプロパティをデータの別の値に変更して、表示がどう変化するかを見てみよう。

「lightgrey」という色名のテキストを変化させる

 HTMLでは次のように、コントローラーの<div>要素の中に、Onsen UIのボタンの<ons-button>を2つ追加する。ng-clickは通常のJavaScriptのonclickに当たる<ons-button>の属性で、実行する関数への呼び出しも通常と同じように""で囲んで指定する。

HTML
<!-- <div ng-controller="MainController">の中-->
<p>{{color}}</p>

<ons-button ng-click="changeRed()">赤に変更</ons-button>
<ons-button ng-click="changeGreen()">緑に変更</ons-button>
[赤に変更]/[緑に変更]ボタンをクリックしたときにchangeRed()/changeGreen()関数を呼び出すHTMLソース(page1.html)

 JavaScriptコードではapp.controller()関数内で、次のように<ons-button>のクリックにより呼び出される関数を、スコープの関数として定義する。この関数では、コントローラーと表示の仲介者であるスコープのcolorプロパティに、データの適切な値を割り当てる。

JavaScript
// app.controller()の関数内の一番下に以下を追記

// スコープの関数を定義
$scope.changeRed = function(){
  $scope.color = $scope.colorData[1].color;  // red
}

$scope.changeGreen = function(){
  $scope.color = $scope.colorData[2].color;  // green
}
ボタンクリック時に呼び出されるchangeRed()/changeGreen()をスコープの関数として定義するJavaScriptコード(index.html)

 ここまでの変更を保存し、デバッガーでアプリを実行する。[赤に変更]ボタンをタップすると、<p>要素のテキストが「red」に変わり、[緑に変更]ボタンをタップすると「green」に変わる。これはpage1.htmlファイルに記述した<p>{{color}}</p>がダイナミックに<p>red</p><p>green</p>に変化した結果を示している。

図3-3 ボタンを追加し、colorプロパティを変化させた
ページ上にある領域の配色を変化させる

 このcolorプロパティの変更はそのまま、実際のカラーの変更に適用できる。次はボタンのタップで<div>要素のカラーを変更してみよう。

 HTMLのコントローラーの<div>要素の中に、次の<div>を追加する。style属性のbackground-colorプロパティには{{color}}というAngular式を割り当てる。

HTML
<!-- <div ng-controller="MainController">の中の一番下に追記-->

<div class="box" style="background-color:{{color}};"></div>
色を反映するために用意する<div>要素のボックス領域(page1.html)

 アプリの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>が赤に変わったところを示している。

図3-4 colorプロパティの変化を実際のカラーの変化に適用した

 以上が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の場合、次のように使用できる。

HTML
<!-- <div ng-controller="MainController">の中-->

<ul>
  <li ng-repeat="data in colorData">
    {{data.id}}:{{data.color}}
  </li>
</ul>
ng-repeatディレクティブの使用例(page1.html)

 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」というテキストが入っている。

図3-5 ng-repeatディレクティブ

 ng-repeatはAngularJS既定のディレクティブだが、ディレクティブは自作することもできる。

ディレクティブの自作

 そのためには、myAppモジュール(=変数app)のdirective()関数にディレクティブ名と関数を渡して、myAppモジュールに自作のディレクティブを登録する。

 次のJavaScriptコードは、要素の背景色をスコープのcolorプロパティに一致させるcolorBoxディレクティブの作成例だ。このコードはこれまでと異なり、app.controller('MainController', function($scope){...});の下に記述する。

JavaScript
// 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;
      });
    }
  }
});
colorBoxディレクティブの作成例(index.html)

 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プロパティの値に変更するようになる。

HTML
<div class="box" color-box></div>
自作ディレクティブの使用例(page1.html)

自作ディレクティブの実行

 ここまでのコードを保存し、デバッガーで実行してボタンをタップする。すると2つの<div>要素のカラーが同じように変化することが分かる。

図3-6 2つめの<div>にcolorBoxディレクティブを使用した

データの追加

 アプリケーションのデータは簡単に追加できる。スコープのデータの配列に、新しいデータをオブジェクトとして追加するだけだ。

 HTMLでは(もちろんコントローラーの<div>内に)次のボタンを追加する。

HTML
<ons-button ng-click="addData()">データを追加</ons-button>
データを追加するためのボタンの追加(page1.html)

 JavaScriptでは、ボタンから呼び出す関数をスコープの関数として定義する。次のコードでは、ボタンのタップで{"id":"4", "color":"yellow"}というオブジェクトを追加し、確認のためにスコープのcolorプロパティを新しいデータに設定している。

JavaScript
$scope.addData = function(){
  $scope.colorData.push({"id":"4", "color":"yellow"});
  $scope.color = $scope.colorData[3].color;
}
データの追加を行うaddData()関数の実装内容(index.html)

 ここまでのコードを保存し、デバッガーでアプリを起動する。[データを追加]ボタンをタップすると、新しいデータが追加され、それがスコープのcolorプロパティに設定されるので、図3-7に示すように、<p>要素のテキストには「yellow」が表示され、2つの長方形は黄色になる。そしてリスト要素には4つめのデータとして「4: yellow」が表示される。

図3-7 データを追加した

AngularJSの機能、サービス

 最後に、Onsen UIを使ったMonacaアプリで役立つAngularJSの機能として、サービスを紹介しておこう。

 アプリで使用するデータは、例えば天気予報アプリなどでは、外部のデータをインターネット経由で取得することになる。AngularJSではこの機能をサービスとして作成し、アプリで利用する。AngularJSのサービスは、インスタンスが1つしかないオブジェクトで表されるので、重複させたくないデータを扱うには最適だ。サービスはまた、複数のコントロール間で使用したいデータの共有スペースとしても利用される。

サービスの作成

 ここまではデータをコントローラー内で直接作成してきたが、ここではサービスを使ってデータを扱えるようにしていこう。そのためにはmyAppモジュール(=変数app)のfactory()関数にサービス名と関数を渡して、次のようにサービスを作成する。

JavaScript
// 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;
});
サービスの作成(index.html)

 この例では「DataService」という名前のサービスを作成している。その機能、つまりデータを参照するプロパティやデータを扱う関数は、app.factory()に渡す関数が返すオブジェクトで定義する。これは今の場合でいうとserviceオブジェクトで、serviceはデータを参照するdataプロパティと、データを返すgetData()関数、データを追加するaddData()関数を持っている。

サービスの使用

 このDataServiceサービスをコントローラーで使用するには、app.controller()に渡す関数の引数として、次のようにDataServiceを渡す。

JavaScript
// サービスを利用するコントローラー
app.controller('MainController', function($scope, DataService){
……省略……
コントローラーでのサービスの使用(index.html)

 するとコントローラーでは、DataServicegetData()関数を使って、スコープのcolorDataプロパティに値を割り当てられるようになる。

JavaScript
$scope.colorData = DataService.getData();
スコープのcolorプロパティ値を、サービスのgetData()関数を使って割り当てる(index.html)

 またスコープのaddData()関数では、DataServiceサービスのaddData()関数が次のように使用できる。

JavaScript
$scope.addData = function(){
  DataService.addData({"id":"4", "color":"yellow"});
  $scope.color = $scope.colorData[3].color;
}
addData()をスコープ関数として定義して、内部でDataServiceサービスのaddData()関数を呼び出す(index.html)

 サービスを利用するコントローラー全体は次のようになる。

JavaScript
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]を参照してください。

Monaca入門:Onsen UI+AngularJSで作るハイブリッドモバイルアプリ(3)
1. Onsen UI+AngularJSで効率的にモバイルアプリが作れるMonaca

HTML+CSS+JavaScriptを使ってスマホやタブレットで動作するアプリが簡単に作成できる「Monaca」を基礎から解説する入門者向け連載(改訂版)がスタート。今回はMonaca/Onsen UI/AngularJSの概要とMonaca IDE、Monacaデバッガーを紹介。

Monaca入門:Onsen UI+AngularJSで作るハイブリッドモバイルアプリ(3)
2. Monacaで作る、初めてのOnsen UIアプリ

新規作成した“初めてのOnsen UIアプリ”プロジェクトの各ファイルをAngularJS流に書き換える。そのアプリをデバッグビルドし、デバイスに実際にインストールする。

Monaca入門:Onsen UI+AngularJSで作るハイブリッドモバイルアプリ(3)
3. 【現在、表示中】≫ Onsen UIの舞台裏で働くAngularJSの世界

AngularJS流のデータ/コントローラー/表示の実装方法と、AngularJSのディレクティブによるHTML要素の操作方法、データの追加、AngularJS機能のサービスについて解説する。

Monaca入門:Onsen UI+AngularJSで作るハイブリッドモバイルアプリ(3)
4. AngularJSの方法でMonacaアプリを作ってみよう(前編)

実践的なMonacaアプリ開発の一例として、オープンデータのWebサービスを使ったアプリの作成方法を説明する。Monacaアプリ開発実践編の第1弾。

Monaca入門:Onsen UI+AngularJSで作るハイブリッドモバイルアプリ(3)
5. AngularJSの方法でMonacaアプリを作ってみよう(中編)

Monacaアプリ開発実践編の第2弾。Web APIを使ってバス停の座標データを取得するアプリの作成手順を解説する。

サイトからのお知らせ

Twitterでつぶやこう!