【AngularJS 】ページにバインドした $scope を別のコントローラと共有・同期したい【Monaca 】【Onsen UI】 | oki2a24 の続編となります
今まで、Monaca に Onsen UI を組み込んで AngularJS を駆使して次の 2 つのデータ共有方法を学習してまいりました♪
- レベル1. 同じページに複数のコントローラー
- レベル2. 異なるページに複数のコントローラー
そして、次の点を学びました。
- ファクトリーオブジェクトは 1 つのみ存在する。
- $scope はファクトリーの参照である。
- ファクトリーまたは $scope のどちらのプロパティの値を変更しても、すべてのコントローラーのデータが即時で同期される
- ファクトリーまたは $scope のどちらにプロパティを追加しても、すべてのコントローラーのデータが即時で同期される
実際のアプリでは、一覧画面でひとつを選択して詳細画面へ行き、編集や削除を行い、一覧画面へ戻る、そのとき詳細画面の変更内容は一覧画面へ反映済み、という動きが普通と存じます。
今回は、そのようなケースを想定して次のような仕様のサンプルを作ります。
- 一覧画面: 一覧データはサーバから取得してメモリに保存(ただしサンプルなのでダミーの JSON ファイルを作って使う)
- 一覧画面 → 詳細画面の移動時: サーバから詳細データを取得してメモリに保存(ただしサンプルなのでダミーの JSON ファイルを作って使う)
- 詳細画面: 内容を変更して「変更」ボタンをタップしたら、サーバへ内容を送信し、詳細メモリ・一覧メモリの内容も更新する。(ただしサンプルなのでサーバへ変更内容は送信しない)
ネットワーク通信は実際には行いません。一覧から詳細への移動時は毎回ダミーの JSON ファイルからデータを取得いたします。したがいまして、詳細画面が表示される時は毎回同じデータです。
レベル3. 異なるページに複数のコントローラーでデータはオブジェクトの配列
ファイル構成
json ディレクトリに JSON ファイルを置いております。これらは REST 通信をしたときに取得される役割の、ダミーデータでございます。サンプルアプリなので作りましたけれども、本来であればサーバから送られてくるデータですので不要ですの。
- index.html
- js/
- app.js
- json/
- note.json
- page0.json
- page1.json
- page2.json
index.html
一覧画面は list.html、詳細画面は detail.htm ですけれども、index.html に 2 つとも書いています。<ons-template id=”list.html”> といった部分です。これによって SPA(Single Page Application)を実現しております。
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<script src="components/loader.js"></script>
<script src="js/app.js"></script>
<link rel="stylesheet" href="components/loader.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<ons-navigator var="navi" page="list.html">
</ons-navigator>
<ons-template id="list.html">
<ons-page>
<ons-toolbar>
<div class="center">一覧</div>
</ons-toolbar>
<div ng-controller="ListController">
<ons-list>
<ons-list-item modifier="chevron" ng-repeat="page in note" ng-click="goDetail(page.id)">
{{page.text}}
</ons-list-item>
</ons-list>
</div>
</ons-page>
</ons-template>
<ons-template id="detail.html">
<ons-page>
<ons-toolbar>
<div class="left"><ons-back-button>Back</ons-back-button></div>
<div class="center">詳細</div>
</ons-toolbar>
<div ng-controller="DetailController">
<ons-list modifier="inset">
<ons-list-item>
<ons-row>
<ons-col>{{page.id}}</ons-col>
<ons-col>{{page.user}}</ons-col>
</ons-row>
</ons-list-item>
<ons-list-item>
<textarea class="textarea--transparent" placeholder="テキスト" style="width: 100%; height: 300px" ng-model="page.text"></textarea>
</ons-list-item>
<ons-list-item>
<input type="text" class="text-input--transparent" placeholder="その他" style="width: 100%" ng-model="page.other" />
</ons-list-item>
</ons-list>
<div style="padding: 10px 9px">
<ons-button modifier="large" ng-click="updatePage()">更新</ons-button>
</div>
</div>
</ons-page>
</ons-template>
</body>
</html>
js/app.js
コントローラーで共有するデータは、NoteModel と PageModel と名づけたファクトリーですの。
NoteModel が一覧画面データ、PageModel が詳細画面データですわ。
最初は、NoteModel の一覧の中に詳細データも含めることでサーバとの通信回数を減らす方針と考えておりました。けれども詳細データに画像などがあり大きなサイズの場合、一覧画面のロード中にそれらも取得するのは時間がかかってしまいます。そこで、一覧用データ、詳細用データと分け、それぞれ REST 通信して取得することといたします。
ポイント1. 共有ファクトリーでの配列の扱い
また、苦労したのは、共有するファクトリーでの配列の扱い方です。
配列を $scope に代入する時はファクトリー自身ではなくファクトリーの配列プロパティを指定する必要がございます。したがって、ファクトリーに予め配列格納用のプロパティを用意しておきます。
レベル1、レベル2ではコントローラーで
$scope.data = Factory;
とすればビューで data.property とすれば Factory.property へアクセスできました。
ところが、配列データを扱う時はこれではアクセスできず、
$scope.data = Factory.array;
とする必要がございました。
ポイント2. 共有ファクトリーへのデータの代入
また、ファクトリーにデータを代入したい時はプロパティを指定する必要があります。$scope に代入する時は不要でしたのですっかり混乱してしまいました。
具体的には、
Factory = data;
では、Factory に新しく data というプロパティが追加されてしまい Factory.data.property1 としないとアクセスできなくなるため、だめです><。
Factory .property1 = data.property1; Factory .property2 = data.property2; Factory .property3 = data.property3; ...
と一つ一つプロパティを指定する必要があります。
以上を踏まえまして、ソースは次のようになりました。
"use strict"
var app = ons.bootstrap();
/**
* 一覧ページで使用する「ノート」オブジェクト
* 複数コントローラーでデータを共有します。
*/
app.factory('NoteModel', function () {
return {
// pages は次のプロパティをもつオブジェクトの配列
// id: 数値、text: 文字列
pages: [],
/**
* NoteModel.pages プロパティの要素を更新します。
* @param {PageModel} ページオブジェクト
*/
updatePage: function (page) {
console.log('★NoteModel.updatePage 関数スタート');
// this.pages の中から id が一致するオブジェクトを更新
for (var i = 0, max = this.pages.length; i < max; i = i + 1) {
if (page.id === this.pages[i].id) {
console.log('page: ' + JSON.stringify(page));
console.log('変更前 this.pages[i]: ' + JSON.stringify(this.pages[i]));
this.pages[i].text = page.text;
}
}
console.log('変更後 this.pages: ' + JSON.stringify(this.pages));
}
}
});
/**
* 詳細ページで使用する「ページ」オブジェクト
* 複数コントローラーでデータを共有します。
*/
app.factory('PageModel', function () {
return {
id: 0,
text: '',
other: '',
user: ''
}
});
/**
* 一覧画面のコントローラーです。
*/
app.controller('ListController', function ($scope, $http, NoteModel, PageModel) {
console.log('★ListController スタート');
// 本来であれば通信してデータを取得
$http.get('json/note.json').then(function (response) {
// データは他のコントローラーで使用するために、
// NoteModel の pages プロパティに代入し、
NoteModel.pages = response.data;
$scope.note = NoteModel.pages;
console.log('NoteModel: ' + JSON.stringify(NoteModel));
});
/**
* 詳細画面へ移動します。
* @param {number} ページ の id
*/
$scope.goDetail = function (id) {
console.log('★goDetail 関数スタート');
// ページデータを取得
// 本来であれば通信してデータを取得
$http.get('json/page' + id + '.json').then(function (response) {
// 移動先のコントローラーへ渡したいため、PageModel にデータを入れる。
// 画面表示はしないため、$scope へは入れない。
// PageModel = response.data; は NG。
// 別コントローラから使用するとき、元のファクトリー(宣言した時の状態)を参照するため。
// また、PageModel.page = response.data; なども NG。
// ファクトリーに page プロパティが追加され、元々の id などには値が入らないため。
PageModel.id = response.data.id;
PageModel.text = response.data.text;
PageModel.other = response.data.other;
PageModel.user = response.data.user;
console.log('NoteModel: ' + JSON.stringify(NoteModel));
console.log('PageModel: ' + JSON.stringify(PageModel));
// 詳細画面へ移動
navi.pushPage('detail.html');
});
};
});
/**
* 詳細画面のコントローラーです。
*/
app.controller('DetailController', function ($scope, NoteModel, PageModel) {
console.log('★DetailController スタート');
$scope.page = PageModel;
console.log('NoteModel: ' + JSON.stringify(NoteModel));
console.log('PageModel: ' + JSON.stringify(PageModel));
/**
* ページデータを更新します。
*/
$scope.updatePage = function () {
// 本来であれば通信して PageModel に対応するレコードを更新
// NoteModel を更新して、一覧に Back した時に表示が変わっているようにする。
NoteModel.updatePage(PageModel);
};
});
json/note.json
一覧画面用データです。本来であれば REST 通信でサーバから取得することを想定しております。ですので、ダミーデータですわ♪
[
{
"id": 0,
"text": "今日は晴れ○"
},
{
"id": 1,
"text": "昨日は曇り■"
},
{
"id": 2,
"text": "明日は雪★"
}
]
json/page0.json
詳細画面用データその 1 です。こちらもダミーデータで、page1.json、page2.json も同様ですの。
{
"id": 0,
"text": "今日は晴れ○",
"other": "暑かった!",
"user": "アン"
}
json/page1.json
{
"id": 1,
"text": "昨日は曇り■",
"other": "洗濯物が乾かなかった...",
"user": "ボブ"
}
json/page2.json
{
"id": 2,
"text": "明日は雪★",
"other": "寒くなりそう。防寒しっかりと!",
"user": "キャシー"
}
おわりに
こちらのページもコントローラー間でのデータ共有方法について、理解を深めるのに役に立ちました。ありがとう存じます!
以上です。

