AngularFire使ってみた

AngularFireとは

特徴

  • RxJS による Angular と Firebase 間のデータのリアルタイム同期
  • 各種プロバイダの認証状態の監視
  • オフラインデータの自動保存
  • ngrx フレンドリー

機能

@NgModules で個別にセットアップする

  • AngularFirestoreModule
  • AngularFireAuthModule
    • 【認証】Firebase Authentication
  • AngularFireDatabaseModule
    • 【旧DB】Realtime Database
  • AngularFireStorageModule
    • 【FileStorage】Cloud Storage (Future release)
  • AngularFireMessagingModule
    • 【Messaging】Cloud Messaging (Future release)

試したこと

  • 開発合宿で作ったお勉強アプリを 生Firebase -> AngularFire に置き換える
    • ユーザ認証
    • データの読み書き
  • 簡単な CRUD のデモを作成

AngularFireAuthModule と AngularFireAuthModule を利用。

デモ

認証

CRUD

認証

AngularFireAuth.authStateを参照する

import { AngularFireAuth } from 'angularfire2/auth';
import * as firebase from 'firebase/app';

// NG: /app の方を呼ばないと正常に動作しない
// import * as firebase from 'firebase';

export class AppComponent {

  constructor(
    public afAuth: AngularFireAuth,
  ) {

  login() {

    this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider());
    // or this.afAuth.auth.signInWithRedirect(new firebase.auth.GoogleAuthProvider());
  }

  logout() {
    this.afAuth.auth.signOut();
  }

}
<div *ngIf="afAuth.authState | async; let user; else showLogin">
  <img [attr.src]="user?.photoURL">
  <span>{{ user.displayName }}!</span>
  <button (click)="logout()">Logout</button>
</div>

<ng-template #showLogin>
  <button (click)="login()">Login with Google</button>
</ng-template>

オブジェクトデータの表示 / 編集

AngularFireObject 型を使う

interface Info {
  appName: string;
  update: Date;
}
export class AppComponent {

  infoRef: AngularFireObject<Info>;
  info$: Observable<Info>;

  constructor(
    private afDb: AngularFireDatabase
  ) {
    this.infoRef = afDb.object('info');
    this.info$ = this.infoRef.valueChanges(); //Observable
  }

  updateInfo(appName: string) {
    this.infoRef.update({
      appName: appName,
      update: new Date()
    });
  }
}
<table>
  <tr>
    <th>appName</th>
    <td>{{(info$ | async)?.appName}}</td>
    <td><input #editedAppName [value]="(info$ | async)?.appName"/></td>
    <td><button (click)="updateInfo(editedAppName.value)">update</button></td>
  </tr>
  <tr>
    <th>update</th>
    <td>{{(info$ | async)?.update}}</td>
  </tr>
</table>

配列データの表示 / 追加 / 全削除

AngularFireList 型を使う

interface Task {
  name: string;
}
export class AppComponent {

  tasksRef: AngularFireList<Task>;
  tasks$: Observable<AngularFireAction<firebase.database.DataSnapshot>[]>;

  constructor(
    private afDb: AngularFireDatabase
  ) {
    this.tasksRef = this.afDb.list<Task>('tasks');
    this.tasks$ = this.tasksRef.valueChanges();
  }

  addTask(taskName: string) {
    this.tasksRef.push({
      name: taskName
    });
  }

  delAllTask() {
    this.tasksRef.remove();
  }

}
<input #newTaskName />
<button (click)="addTask(newTaskName.value)">add</button>
<button (click)="delAllTask()">all del</button>
<table>
  <tr *ngFor="let task of tasks$ | async">
    <td>{{task.name}}</td>
  </tr>
</table>

配列データの修正 / 削除

this.tasks$ = this.tasksRef
  .snapshotChanges()
  .map(changes =>
    changes.map(c => ({key: c.payload.key, ...c.payload.val()}))
  );
delTask(key: string) {
  this.tasksRef.remove(key);
}

saveTask(key: string, taskName: string) {
  this.tasksRef.update(key, {
    name: taskName
  });
}
<table>
  <tr *ngFor="let task of tasks$ | async">
    <td>{{task.name}}</td>
    <td><button (click)="delTask(task.key)">del</button></td>
    <td><input #editedTaskName [value]="task.name" /></td>
    <td><button (click)="saveTask(task.key, editedTaskName.value)">update</button></td>
  </tr>
</table>

AngularFire のメソッドでデータを絞り込む

this.filterTaskName$ = new BehaviorSubject(null);
this.tasks$ = this.filterTaskName$.switchMap(filterTaskName =>
  this.afDb.list<Task>('tasks', ref =>
    filterTaskName ? ref.orderByChild('name').equalTo(filterTaskName) : ref
  )
  .snapshotChanges()
  .map(changes =>
    changes.map(c => ({key: c.payload.key, ...c.payload.val()}))
  )
);
filterTask(filterTaskName: string|null) {
  this.filterTaskName$.next(filterTaskName);
}
<input #filterText (keyup)="filterTask(filterText.value)" />

フィルター

this.filterTaskName$ = new BehaviorSubject(null);
this.tasks$ = this.filterTaskName$.switchMap(filterTaskName =>
  this.tasksRef
    .snapshotChanges()
    .map(changes => changes
      .map(c => ({key: c.payload.key, ...c.payload.val()}))
      .filter(item => !filterTaskName || (new RegExp(filterTaskName, 'i')).test(item.name))
    )
);

感想

  • 良い点
    • RxJS ベースになったのでソースがスッキリした
    • 型が扱えてうれしい(機能調べるときの補完とか)
    • angular-cli と 合わせて使うとかなり手軽にCRUD+認証付きアプリが作れる
  • 良くない点
    • 日本語情報
      • ググるといろいろあるけど微妙に古くなってまんま利用できないのが多い
    • 公式ページ
      • 情報の所在がわかりづらい
      • リファレンスがない
      • 型情報とかソースみないと分からないのがあったりする