第4回ではデコレーターについて解説しました。今回はフォーマッターとサービスについて説明します。今までハードコーディングしていたレシピのデータをサービスから取得し、フォーマッターを使ってデータをフィルタリングします。
今回の元記事は5. Introducing Formatters and Servicesです。
まずはサンプルアプリを動かしてみよう
こちらにサンプルアプリのソースコードがあります。リポジトリをクローンして、Chapter_05ディレクトリで> pub serve
すればサンプルアプリが起動します。
レシピを名前で絞り込むフィルターと、カテゴリで絞り込むフィルターが追加されています。
フォーマッターを理解する
組み込みのフォーマッター
AngularDartには便利なフォーマッターがすでに組み込まれています。Currencyフォーマッターは数字を通貨のように整形します。Dateは日付を整形します。その他にも便利なフォーマッターがあります。今回の名前での絞り込みにはFilterフォーマッターを使います。Filter
フォーマッターはリストの中からプロパティが指定された値と一致するものをフィルタリングしてくれます。
まずはじめにフィルターに使うモデルをinput
に設定します
<input id="name-formatter" type="text" ng-model="nameFilterString">
そしてフォーマッターをng-repeat
に与えます。
<ul>
<li class="pointer" ng-repeat="recipe in recipes | filter:{name:nameFilterString}">
...
</li>
</ul>
最後にコンポーネントにモデルを宣言します
String nameFilterString = "";
独自フォーマッターを作る
組み込みフォーマッターは便利ですが、アプリケーションの中ではもっと目的に特化したフォーマッターが必要になることがあります。今回はレシピをカテゴリで絞り込むためにCategoryFilter
という新しいフォーマッターを作成します。
AngularDartのフォーマッターはシンプルな実装からなり、次のcall
メソッドを実装するだけで動作します。
class MyFormatter {
call(valueToFormat, optArg1, ..., optArgN) { ... }
}
第1引数はフォーマッターの対象となるもので、今回はレシピのリストです。残りの引数はオプショナルな引数で、HTML側から与えられます。今回はオプショナルパラメータを1つだけ使い、表示するレシピをフィルタリングします。
List call(recipeList, filterMap) {
if (recipeList is Iterable && filterMap is Map) {
// If there is nothing checked, treat it as "everything is checked"
bool nothingChecked = filterMap.values.every((isChecked) => !isChecked);
return nothingChecked
? recipeList.toList()
: recipeList.where((i) => filterMap[i.category] == true).toList();
}
return const [];
}
フォーマッターとしてクラスを定義するには@Formatter
アノテーションを付け、HTML側での名前を設定します。
@Formatter(name: 'categoryfilter')
class CategoryFilter { ... }
クラスを作ったらモジュールにも忘れずにバインドします。
class MyAppModule extends Module {
MyAppModule() {
...
bind(CategoryFilter)
...
}
}
HTML側にフィルタリング用のマップのモデルを設定します。
<div>
<label>Filter recipes by category
<span ng-repeat="category in categories">
<label>
<input type="checkbox"ng-model="categoryFilterMap[category]">
{{category}}
</label>
</span>
</label>
</div>
独自フォーマッターは組み込みフォーマッターと全く同じように呼び出せます。
<li class="pointer" ng-repeat="recipe in recipes | categoryfilter:categoryFilterMap">
フォーマッターは連結することができます。
<li class="pointer"
ng-repeat="recipe in recipes | orderBy:'name' | filter:{name:nameFilterString} | categoryfilter:categoryFilterMap">
Httpサービスを使う
今回はレシピとカテゴリのデータを外部のJSONファイルからHTTP通信で読み込んでいます。HTTP通信を使うには組み込みのHttpサービスを使います。
はじめにrecipe-bookコンポーネントからHttp
サービスにアクセスできるようにします。コンストラクタにHttp
型の引数を取るだけです。AngularDartは型情報を元に実行時に動的に依存性の注入を行ってくれます。モジュールへの変更は必要ありません。
class RecipeBookComponent {
final Http _http;
...
RecipeBookComponent(this._http) {
_loadData();
}
...
}
レシピのデータを取得する処理は次のように書きます。
void _loadData() {
recipesLoaded = false;
...
_http.get('recipes.json')
.then((HttpResponse response) {
recipes = response.data.map((d) => new Recipe.fromJson(d)).toList();
recipesLoaded = true;
})
.catchError((e) {
recipesLoaded = false;
message = ERROR_MESSAGE;
});
...
}
Http
のget
は次のように書きます。Dartの組み込みのFuture
を使っているので非同期に実行されます。
_http.get(URL)
.then((value) {
// use value
})
.catchError((e) {
// handle error
});
データの取得は非同期に実行されるので、最初に同期的にモデルを初期化する必要があります。
List categories = [];
List<Recipe> recipes = [];
HTML側ではレシピとカテゴリ両方の取得が終わったかどうかでng-switch
を使いビューを切り替えています。ng-switch
に与えられた値はng-switch-when
で一致するビューを表示します。
<div recipe-book ng-cloak>
<div ng-switch="recipesLoaded && categoriesLoaded">
<div ng-switch-when="false">
{{message}}
</div>
<div ng-switch-when="true">
<h3>Recipe List</h3>
...
</div>
</div>
</body>
Angularの機能
ng-cloak
HTMLが読み込まれてからバインディングが始まるまでの時間、{{ }}
が見えることを防ぐための仕組みです。ng-cloak
が付けられた要素の中身はAngularがレンダリングを終えるまで表示されません。
まとめ
- フォーマッターはモデルを整形する
- HTTP通信にはHttpサービスを使う
- コンストラクタの引数の型をもとにサービスへの依存性が注入される
演習
ここまでの内容が理解できているかどうか、次のことを試してみましょう
- レシピの中の“sugar”と“powdered sugar”を“maple syrup”に置き換えるフォーマッターを作ってみましょう。ヒント: String#replaceAll()
- レシピの指示中のファーレンハイト温度(degree F)をセルシウス温度(degree C)に変換するフォーマッターを作ってみましょう。変換した値は5の倍数に丸めてください。(例: 300 degrees F は150 Cになります) ヒント: Dartの正規表現を使ってみましょう。
- 大きなパーティを開くために、レシピの分量を人数倍にできるようにしましょう。ヒント: まず、レシピのモデルを変更し、Ingredientに量と単位、そして材料の名前の3つのフィールドを追加しなければなりません。次に、ユーザーが分量を2倍、3倍にできるよう、
multipilier
の入力エリアを追加しましょう。最後に入力された数字を元に分量を倍にするフォーマッターを作成しましょう。 - 最後に、1と2のフォーマッターをチェックボックスで切り替えられるようにしてみましょう。
第6回は6. Creating Views
を元にバージョン1.0に置き換えて解説します。