「angularfire2を使ってTOUR OF HEROESをいじってみた」を読んで、書き留めておいたネタをボツにしようと思ってましたが、正月早々ボツねたで下書きが満杯だったので、せっかくなので放出しときます。まあ、Dart版ということで、、、と思ったらAngular Dartの Firebase Deploy関連で既に詳細な「Angular2とFirebase hostingでSPA開発環境を整える」がありました。(汗
ええい。おとそ気分で、とにかく放出!
ソースはここにあります。
https://github.com/tatsu/tour-of-heroes-in-dart
1/21追記
Deploy Angular Dart Tutorial to Firebase 改良メモでHeroServiceまわりを改良していますが、チュートリアルなので最小限の修正で試せる本稿はこのままにします。
Angular DartのチュートリアルApp、Tour of Heroesがいいところで終わってしまっているので、in-memoryデータをFirebaseのDatabaseに置き換えてdeployし、一通り完結させてみました。TypeScriptも似たようなもんなので、よければ参考にして下さい。
と、こっぱずかしくなるような上から目線で書いてましたが、言うまでもなくTypeScriptのTutorialと、Firebaseディプロイに関しては上記の記事を参考にして下さい。
前提
Angular Dartのチュートリアルを実施します。
https://webdev.dartlang.org/angular/tutorial
もしくは、以下からソースを取得します。
https://github.com/angular-examples/toh-6
Firebase対応
FirebaseコンソールでプロジェクトのOverviewで"Add Firebase to your web app"を選択して表示されるダイアログを参考にしてください。
<script src="https://www.gstatic.com/firebasejs/3.6.4/firebase.js"></script>
main() {
firebase.initializeApp(
apiKey: "YourApiKey",
authDomain: "YourAuthDomain",
databaseURL: "YourDatabaseUrl",
storageBucket: "YourStorageBucket");
bootstrap(AppComponent);
}
Dartのラッパーライブラリを追加してpub get
します。
dependencies:
firebase: ^3.0.0
Firebase Realtime Database対応
ソース変更はサービスのみで、メソッドの呼び出し手順に変更はありません。
HeroServiceクラスで、DatabaseとReferenceのインスタンスを追加し、コンストラクタで設定しています。_heroesは、リアルタイムに更新させるつもりでインスタンス変数にしています。
Deploy Angular Dart Tutorial to Firebase 改良メモで改良していますのでそちらをご参照ください。
_idMaxはチュートリアルマターでidの最大値を覚えておくためのものです。
firebase.Database _fbDatabase;
firebase.DatabaseReference _fbRefHeroes;
List<Hero> _heroes;
int _idMax;
HeroService() {
_fbDatabase = firebase.database();
_fbRefHeroes = _fbDatabase.ref("app/heroes");
}
アクセスメソッドはそれぞれ以下のとおりです。
Future<Hero> getHero(int id) async =>
(await getHeroes()).firstWhere((hero) => hero.id == id);
Future<List<Hero>> getHeroes() async {
try {
final List<Hero> heroes = [];
_idMax = 0;
var e = await _fbRefHeroes.onValue.first;
e.snapshot.forEach((child) {
Hero hero = new Hero.fromJson(child.val());
heroes.add(hero);
_idMax = max(hero.id, _idMax);
});
_heroes = heroes;
} catch (e) {
throw _handleError(e);
}
return new List<Hero>.from(_heroes);
}
Future<Hero> create(String name) async {
try {
Hero hero = new Hero.fromJson({'id': ++_idMax, 'name': name});
await _fbRefHeroes.push(hero.toJson());
return hero;
} catch (e) {
throw _handleError(e);
}
}
Future<Hero> update(Hero hero) async {
try {
var e = await _fbRefHeroes.orderByChild('id').equalTo(hero.id).onValue.first;
e.snapshot.forEach((child) {
child.ref.update(hero.toJson());
});
return hero;
} catch (e) {
throw _handleError(e);
}
}
Future<Null> delete(int id) async {
try {
var e = await _fbRefHeroes.orderByChild('id').equalTo(id).onValue.first;
e.snapshot.forEach((child) {
child.ref.remove();
});
} catch (e) {
throw _handleError(e);
}
}
HeroSearchServiceクラスでも、DatabaseとReferenceのインスタンスを追加し、コンストラクタで設定しています。
firebase.Database _fbDatabase;
firebase.DatabaseReference _fbRefHeroes;
HeroSearchService() {
_fbDatabase = firebase.database();
_fbRefHeroes = _fbDatabase.ref("app/heroes");
}
searchメソッドでは、valueイベントでheroesデータ全体を回しながら、一致するデータを抜き出しています。Firebaseには部分一致でQueryをかけるズバリな方法はいまのところなさそうです。(あってる?)まあ、しかし、かなりイケてないです。ご指摘お願いします。
Future<List<Hero>> search(String term) async {
final regExp = new RegExp(term, caseSensitive: false);
try {
final List<Hero> heroes = [];
var e = await _fbRefHeroes.onValue.first;
e.snapshot.forEach((child) {
Hero hero = new Hero.fromJson(child.val());
if (hero.name.contains(regExp)) {
heroes.add(hero);
}
});
return heroes;
} catch (e) {
throw _handleError(e);
}
}
}
削除
それから認証を設定せずに読み書きができるように、Realtime Databaseの公開アクセスルールで、少なくとも"/app/heroes"の下を誰でも読み書きできるようにしておく必要があります。ディプロイ時に指定することもできるようですが、自分はコンソールで確認しながら設定しています。インデックスを張らないとうらで警告(というかサジェスチョン)がでてますが、任意です。
{
"rules": {
"app": {
".read": true,
".write": true,
"heroes": {
".indexOn": ["id",]
},
},
".read": "auth != null",
".write": "auth != null",
},
}
コンソールからインポートできるサンプルデータファイルをソースルートに置いてあります。
{
"app" : {
"heroes" : {
"-KY2V7jtkPQagx0MLEnu" : {
"id" : 11,
"name" : "Mr. Nice"
},
...snip...
"-KY2VRm9YBRZ5WpzZ90L" : {
"id" : 20,
"name" : "Tornado"
}
}
}
}
FirebaseのDatabaseアクセスについては、ググってもなかなか参考にできる情報が少ないようで、あーでもないこーでもないで一応動作していますが、かなりぷぷぷな感じだと思います。いろいろご指摘いただけると勉強になります。
おまけ#1
Dart Routerを使用すると、リロードで404 Not Foundになってしまう問題があり、AppComponentで指定していたROUTER_PROVIDERSとHashLocationStrategyををbootstrapで指定して対応しています。
https://github.com/angular/angular/issues/7728
bootstrap(AppComponent, [
ROUTER_PROVIDERS,
provide(LocationStrategy, useClass: HashLocationStrategy)
]);
おまけ#2
cssをscssに変更してます。
dependencies:
dart_sass_transformer: any
transformers:
- dart_sass_transformer