Deploy Angular Dart Tutorial to Firebase

「[angularfire2を使ってTOUR OF HEROESをいじってみた](http://qiita.com/can_i_do_web/items/d846fad680f5648114a6)」を読んで、書き留めておいたネタをボツにしようと思ってましたが、正月早々ボツねたで下書きが満杯だったので、せっかくなので放出しときます。まあ、Dart版ということで、、、と思ったらAngular Dartの Firebase Deploy関連で既に詳細な「[Angular2とFirebase hostingでSPA開発環境を整える](http://qiita.com/ntaoo/items/d11a0cd3fa92f98372a4)」がありました。（汗

ええい。おとそ気分で、とにかく放出！

ソースはここにあります。
https://github.com/tatsu/tour-of-heroes-in-dart

1/21追記
[Deploy Angular Dart Tutorial to Firebase 改良メモ](http://qiita.com/tatsu/items/91bf9b01fd3e5d55cd09)で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"を選択して表示されるダイアログを参考にしてください。

```web/index.html
    <script src="https://www.gstatic.com/firebasejs/3.6.4/firebase.js"></script>
```

```web/main.dart
main() {
  firebase.initializeApp(
      apiKey: "YourApiKey",
      authDomain: "YourAuthDomain",
      databaseURL: "YourDatabaseUrl",
      storageBucket: "YourStorageBucket");

  bootstrap(AppComponent);
}
```

Dartのラッパーライブラリを追加して`pub get`します。

```pubspec.yaml
dependencies:
  firebase: ^3.0.0
```

## Firebase Realtime Database対応

ソース変更はサービスのみで、メソッドの呼び出し手順に変更はありません。

HeroServiceクラスで、DatabaseとReferenceのインスタンスを追加し、コンストラクタで設定しています。\_heroesは、リアルタイムに更新させるつもりでインスタンス変数にしています。

> [Deploy Angular Dart Tutorial to Firebase 改良メモ](http://qiita.com/tatsu/items/91bf9b01fd3e5d55cd09)で改良していますのでそちらをご参照ください。

\_idMaxはチュートリアルマターでidの最大値を覚えておくためのものです。

```hero_service.dart
  firebase.Database _fbDatabase;
  firebase.DatabaseReference _fbRefHeroes;
  List<Hero> _heroes;
  int _idMax;

  HeroService() {
    _fbDatabase = firebase.database();
    _fbRefHeroes = _fbDatabase.ref("app/heroes");
  }
```

アクセスメソッドはそれぞれ以下のとおりです。

```hero_service.dart
  Future<Hero> getHero(int id) async =>
      (await getHeroes()).firstWhere((hero) => hero.id == id);
```

```hero_service.dart
  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);
  }
```

```hero_service.dart
  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);
    }
  }
```

```hero_service.dart
  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);
    }
  }
```

```hero_service.dart
  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のインスタンスを追加し、コンストラクタで設定しています。

```hero_search_service.dart
  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);
    }
  }
}
```

```in_memory_data_service.dart
削除
```

それから認証を設定せずに読み書きができるように、Realtime Databaseの公開アクセスルールで、少なくとも"/app/heroes"の下を誰でも読み書きできるようにしておく必要があります。ディプロイ時に指定することもできるようですが、自分はコンソールで確認しながら設定しています。インデックスを張らないとうらで警告（というかサジェスチョン）がでてますが、任意です。

```
{
  "rules": {
    "app": {
      ".read": true,
      ".write": true,
      "heroes": {
        ".indexOn": ["id",]
      },
    },
    ".read": "auth != null",
    ".write": "auth != null",
  },
}
```

コンソールからインポートできるサンプルデータファイルをソースルートに置いてあります。

```sample-heroes.json
{
  "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

```main.dart
  bootstrap(AppComponent, [
    ROUTER_PROVIDERS,
    provide(LocationStrategy, useClass: HashLocationStrategy)
  ]);
```

## おまけ#2

cssをscssに変更してます。

```pubspec.yaml
dependencies:

  dart_sass_transformer: any

transformers:
- dart_sass_transformer

```