maesblog

AngularFireでFirestoreのCRUD処理を実装する【Angular + Firebase】

先日当ブログで、『Angular + Firebase事始め – Firestoreのデータを取得し、Firebase Hostingにデプロイするまで』という記事を投稿しました。この記事では、開発環境の構築方法、AngularとFirebase(Firestore)との連携方法、デプロイ方法など主に開発の前段階の内容を扱いました。今回は少し先に進んで、実際にデータを操作する方法を紹介したいと思います。AngularFire (AngularFire2) というAngular公式のFirebase用ライブラリを使ってCRUDを行う処理をまとめてみました。

はじめに

今回の記事は、主にAngularFireの機能に焦点を当てています。AngularとFirebaseについては、すでに扱える状態になっていることが前提となっています。AngularとFirebaseでの開発環境の構築方法については、当ブログの記事を参考にしてもらえればと思います。

それから、データベースとしてFirebaseで用意されているCloud Firestoreを使用します。Firestoreは、「クエリと自動スケーリング機能を備えた次世代のRealtime Datebase」と紹介されていて、MongoDBと同じようにドキュメント指向のいわゆるNoSQLのデータベースです。

この後の説明では、ドキュメント単位コレクション単位といった言葉を使っています。いわゆるリレーショナルデータベースで言うと、ドキュメントはデータ、コレクションはテーブルになります。

AngularFireを使う準備

AngularFireは、Angular公式のFirebase用ライブラリです。AngularFireには、AngularからFirebaseを操作するための様々なAPIが用意されています。CRUDの処理もAngularFireを使うことで、比較的に容易に書くことができます。まずはこのAngularFireをAngularのプロジェクトで使えるようにする方法から説明していきます。

AngularFireのインストール

Angularのプロジェクトディレクトリ内で以下のコマンドを実行します。FirebaseとAngularFireがインストールされます。

$ npm install firebase @angular/fire --save

モジュールの読み込み

インストールしたAngularFireから以下のモジュールをコンポーネントに読み込みます。これらの詳細は後ほど説明します。

import { Component } from '@angular/core';
import {
  AngularFirestore,
  AngularFirestoreCollection,
  AngularFirestoreDocument,
} from '@angular/fire/firestore';
import { Observable } from 'rxjs';

設定ファイルを用意する

AngularでFirebaseを使用するために、/src/environments/environment.tsファイル(環境ごとに使い分けたい情報を登録するためのファイル)を開いて、Firebaseの設定情報を環境変数として登録します。

export const environment = {
  production: false,
  firebase: {
    apiKey: '<your-key>',
    authDomain: '<your-project-authdomain>',
    databaseURL: '<your-database-URL>',
    projectId: '<your-project-id>',
    storageBucket: '<your-storage-bucket>',
    messagingSenderId: '<your-messaging-sender-id>'
  }
};

NgModuleに登録する

コンポーネントに読み込んだモジュールや設定ファイルをコンポーネントを宣言しているNgModuleファイルに登録します。

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { AngularFireModule } from '@angular/fire';
import { AngularFirestore } from '@angular/fire/firestore';
import { environment } from '../environments/environment';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AngularFireModule.initializeApp(environment.firebase),
  ],
  providers: [AngularFirestore],
  bootstrap: [AppComponent],
})
export class AppModule {}

詳細については、AngularFireのドキュメントや当ブログの関連記事をご確認ください。

以上で、AngularFireを使うことができるようになりました。

Firestoreのデータを扱えるようにする

インターフェースとデータを用意する

まずはデータを準備していきます。たとえば以下のようにデータのインターフェースを定義します。

interface Item {
  id: string;
  name: string;
  age: number;
}

Firestore上でも同様に上記のように定義して、いくつかデータをコンソールを使って登録しておきます。データが登録してあれば、後々データの取得が試しやすくなります。今回の説明では、コレクションIDをitemsとしています(データを取得するときに必要になります)。

AngularFirestoreをDIする

AngularFireでFirestoreを扱うためのサービスとしてAngularFirestoreが用意されています。このAngularFirestoreをコンポーネントで使えるようにDI(依存性注入)します。

export class AppComponent {
  constructor(private afs: AngularFirestore) {}
}

Firestoreからデータを取得する

AngularFirestoreを使うと、データをドキュメント単位コレクション単位で扱えるようになります。AngularFireでは、それぞれ以下のように型が用意されています。

  • AngularFirestoreDocument: Firestore SDKのDocumentReferenceをラップしたサービス。データをドキュメント単位で扱う際に使用する。
  • AngularFirestoreCollection: Firestore SDKのCollectionReferenceQueryをラップしたサービス。データをコレクション単位で扱う際に使用する。

上記の型を使って、Firestoreから取得したドキュメント、コレクションを格納するプロパティ(オブジェクト)をそれぞれ定義します。Firestore上のドキュメント、コレクションはAngularFirestoreで用意されているdoc()メソッド、collection()メソッドを使って取得します(それぞれ引数には取得したいドキュメントID、コレクションIDをpathで指定します)。

export class AppComponent {
  /** 取得したドキュメントを格納 */
  private itemDocument: AngularFirestoreDocument<Item>;
  /** 取得したコレクションを格納 */
  private itemsCollection: AngularFirestoreCollection<Item>;

  constructor(private afs: AngularFirestore) {
    /** items/39z9wtr35SAeAUjcWcThドキュメントを取得してitemDocumentに代入 */
    this.itemDocument = afs.doc<Item>('items/39z9wtr35SAeAUjcWcTh');
     /** itemsコレクションを取得してitemDocumentに代入 */
    this.itemsCollection = afs.collection<Item>('items');
  }
}

データを参照する(Read)

データを参照するには、ドキュメント、コレクションをそれぞれ格納したitemDocumentオブジェクト、itemsCollectionオブジェクトのvalueChanges()メソッドを呼び出します。データがobservable型に変換され、ストリームとして扱えるようになります。

valueChanges()メソッドは、以下のようにconstructorngOnInit内でを呼び出しておくようにしておくと、コンポーネントの初期マウント時からデータの参照が行えるようになって便利です。

export class AppComponent {
  /** 取得したドキュメントを格納 */
  private itemDocument: AngularFirestoreDocument<Item>;
  /** 取得したコレクションを格納 */
  private itemsCollection: AngularFirestoreCollection<Item>;

  /** ドキュメントのストリームを格納 */
  item: Observable<Item>;
  /** コレクションのストリームを格納 */
  items: Observable<Item[]>;

  constructor(private afs: AngularFirestore) {
    /** items/39z9wtr35SAeAUjcWcThドキュメントを取得してitemDocumentに代入 */
    this.itemDocument = afs.doc<Item>('items/39z9wtr35SAeAUjcWcTh');
    /** itemsコレクションを取得してitemDocumentに代入 */
    this.itemsCollection = afs.collection<Item>('items');

    /** 取得したドキュメントをストリームに変換 */
    this.items = this.itemDocument.valueChanges();
    /** 取得したコレクションをストリームに変換 */
    this.item = this.itemsCollection.valueChanges();
  }
}

テンプレートに反映させる

参照に関しては、実際にテンプレートを通してデータを表示するところまでが一連の処理となります。データはストリームになっているので、テンプレート側ではasync pipeを使って、データを参照すると良いでしょう(もちろんviewModel側でサブスクライブしても良いです)。

ドキュメント単位でデータを取得した場合

<p>name: {{ (item | async)?.name }} / age: {{ (item | async)?.age }}</p>

コレクション単位でデータを取得した場合

<ul>
  <li *ngFor="let item of (items | async)">
    name: {{ item.name }} / age: {{ item.age }}
  </li>
</ul>

参照に関しては、コンポーネント生成時にvalueChanges()メソッドを呼び出してデータをストリーム化し、テンプレート側ではasync pipeを使ってデータの状態を監視して表示するようにしておけば、とりあえず後は何もする必要がありません。

今回は、データの参照するのにvalueChanges()メソッドを使用しましたが、snapshotChanges()メソッドといったものもあります。こちらは、ドキュメントIDなどのメターデータが含まれた状態でデータを取得したい場合に使用します。

データを生成する(Create)

ここからはメソッドを作る形でサクッと説明していきます。

ドキュメント単位でデータを生成する場合

作成したいデータを引数に指定して、add()メソッドを呼び出すことで、Firestoreにデータを新たに生成することができます。生成したいデータは、作成したメソッドの引数を通して受け取るようにすると良いでしょう。

addItem(item: Item): void {
  this.itemDocument.add(item);
}

コレクション単位でデータを生成する場合

同様に、作成したいデータを引数に指定して、add()メソッドを呼び出すことで、Firestoreにデータを新たに生成することができます。

addItem(item: Item): void {
  this.itemsCollection.add(item);
}

なお、参照時にvalueChanges()メソッドを使用していると、ドキュメントIDが取得できないので、ドキュメントIDをデータとして持っておきたいところです。その場合は、以下のようにdoc()メソッドで任意のIDを指定してデータを生成することができます。AngularFirestoreで用意されてるcreateId()メソッドを使用すると、自動でIDを割り振ってくれます。

addItem(item: Item): void {
  const id = this.afs.createId();
  const item: Item = {
    id,
    name: item.name,
    age: item.age,
  };
  this.itemsCollection.doc(id).set(item);
}

データを更新する(Update)

ドキュメント単位でデータを更新する場合

更新したいデータを引数に指定して、update()メソッドを呼び出すことで、データを更新することができます。

updateItem(item: Item): void {
  this.itemDocument.update(item);
}

コレクション単位でデータを更新する場合

同様に、更新したいデータを引数に指定して、update()メソッドを呼び出すことで、データを更新することができます。その際に、doc()メソッドを使って、更新したいドキュメントのドキュメントIDを指定します。

updateItem(item: Item): void {
  this.itemsCollection.doc(item.id).update(item);
}

データを削除する(Delete)

ドキュメント単位でデータを削除する場合

delete()メソッドを呼び出すことで、データを削除することができます。

deleteItem(item: Item): void {
  this.itemDocument.delete();
}

コレクション単位でデータを削除する場合

同様に、delete()メソッドを呼び出すことで、データを削除することができます。その際に、doc()メソッドを使って、更新したいドキュメントのドキュメントIDを指定します。

deleteItem(item: Item): void {
  this.itemsCollection.doc(item.id).delete();
}

CRUDを実装したサンプル紹介

上記を踏まえて、 CRUDを実装したサンプルを作成しました。

viewModel(app.component.ts)のソースコードは以下の通りです。詳細はソースコード内のコメントを見てください。

import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import {
  AngularFirestore,
  AngularFirestoreCollection,
} from '@angular/fire/firestore';
import { Observable } from 'rxjs';

/** 扱うデータの型 */
interface Item {
  id: string;
  name: string;
  age: number;
}

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
})
export class AppComponent implements OnInit {
  /** コレクション */
  private itemsCollection: AngularFirestoreCollection<Item>;
  /** コレクションのObservable */
  items: Observable<Item[]>;

  /** フォームコントロール */
  name = new FormControl('');
  age = new FormControl('');
  updatedName = new FormControl('');
  updatedAge = new FormControl('');

  /** 更新するドキュメントID */
  updatedId: string;

  /** ボタンがdisableかどうか */
  isDisabledDelete = false;
  isDisabledEdit = true;

  /** AngularFirestoreをDI */
  constructor(private afs: AngularFirestore) {
    /** itemsコレクションを取得してitemsCollectionに代入 */
    this.itemsCollection = afs.collection<Item>('items');
  }

  ngOnInit(): void {
    /** Read: データを参照(ストリームに変換) */
    this.items = this.itemsCollection.valueChanges();
  }

  /** Create: データを追加 */
  addItem(): void {
    const id = this.afs.createId();
    const item: Item = {
      id,
      name: this.name.value,
      age: this.age.value,
    };
    this.itemsCollection.doc(id).set(item);
    this.name.setValue('');
    this.age.setValue('');
  }

  /** 編集用のフォームにデータを表示 */
  editItem(item: Item): void {
    this.updatedName.setValue(item.name);
    this.updatedAge.setValue(item.age);
    this.updatedId = item.id;
    this.isDisabledDelete = true;
    this.isDisabledEdit = false;
  }

  /** Update: データを更新 */
  updateItem(): void {
    const item = {
      id: this.updatedId,
      name: this.updatedName.value,
      age: this.updatedAge.value,
    };
    this.itemsCollection.doc(this.updatedId).update(item);
    this.updatedName.setValue('');
    this.updatedAge.setValue('');
    this.updatedId = null;
    this.isDisabledDelete = false;
    this.isDisabledEdit = true;
  }

  /** データを削除 */
  deleteItem(item: Item): void {
    this.itemsCollection.doc(item.id).delete();
  }
}
app.component.ts

View(app.component.html)のソースコードは以下の通りです。 

<div>
  name: <input type="text" [formControl]="name" /> age:
  <input type="text" [formControl]="age" />
  <button (click)="addItem()">Add</button>
</div>
<hr />
<ul>
  <li *ngFor="let item of (items | async)">
    name: {{ item.name }} / age: {{ item.age }}
    <button (click)="editItem(item)">Edit</button>
    <button (click)="deleteItem(item)" [disabled]="isDisabledDelete">
      Delete
    </button>
  </li>
</ul>

<div>
  name: <input type="text" [formControl]="updatedName" /> age:
  <input type="text" [formControl]="updatedAge" />
  <button (click)="updateItem()" [disabled]="isDisabledEdit">Update</button>
</div>
app.component.html

このサンプルのソースコードは、GitHubにもアップしてありますので、こちらも合わせてご確認ください。 

ユニットテストコードも書きましたので、参考にしてください。

まとめ

以上、AngularFireを使ったAngular + FirestoreでのCRUD処理の書き方となります。ドキュメント単位、コレクション単位といったようにそれぞれ2通りの処理の書き方を紹介しましたが、基本的にはコレクション単位(AngularFirestoreCollection)で扱うことの方が多いんじゃないかなと思います。今回紹介したサンプルでもコレクション単位でデータを扱っています。

CRUD処理が書けるようになると、開発するアプリケーションの機能の幅も広がります。これだけでもそれなりの機能を持ったアプリを開発することができるようになりますが、AngularFirestoreCollectionでは、さらにwhereやorderbyなどを使ったqueryでのデータ取得にも対応しています。次はこのqueryの使い方を試してみたいと思っているところです。余裕があれば、記事にして紹介できればと思っています。

関連記事

コメント

  • 必須

コメント