🐝

Angular アプリを Firebase と連携させて Cloud Firestore を使う

2022/05/03に公開

Angular アプリと Firebase を連携させてデータベースでCRUD処理を行うための手順についてまとめておきます。

データベースには Cloud Firestore を使用します。

動作環境

今回は以下のバージョンを利用。

  • node: v14.15.3
  • Angular: 13.3.0
  • Firebase: ^9.4.0
  • AngularFire: ^7.2.1

AngularFire の README に利用可能なバージョンが書いてあります。バージョンがずれていると動作しないので注意が必要です。

Firebase側の設定

  1. プロジェクトを作成

  2. </>(ウェブ)からアプリを登録

    Firebase Hosting も設定するにチェック

  3. ローカルで Firebase CLI のインストール

    既にインストール済みだったのでこのバージョンで使用する。

    % firebase --version
    10.4.0
    
  4. Firebase CLI から Google にログイン

    ログイン済みだとメールアドレスが表示される。

    % firebase login 
    Already logged in as xxxxx@gmail.com
    

アプリ側の設定

  1. Angular で Fireabse を扱うためのライブラリをインストール

    ng add @angular/fire
    

    ng add コマンドを使うことで設定を自動で行ってくれるので便利。

  2. angular.json を開いて script の下あたりに設定を追加

    "allowedCommonJsDependencies": [
    	"firebase",
    	"@firebase/database"
    ]
    
  3. 型定義ファイルをインストール

    npm i @types/angularfire --save-dev
    
  4. 環境変数をセット

    Firebaseのプロジェクトの設定 > マイアプリ > 構成 から firebaseConfig の値が app/environments/environment.ts に fireabse プロパティとしてセットされている事を確認。

    ng add コマンドを実行した際に書き込まれているはず。

    export const environment = {
    	production: false,
    	apiKey: ...
      authDomain: ...
      databaseUrRL: ... 
    
  5. モジュールを有効にする

    app/app.modules.ts で AngualrFire のモジュールを設定する。

    import { AngularFireModule } from '@angular/fire/compat';
    import { environment } from '../environments/environment';
    
    imports: [
    	(略)
      AngularFireModule.initializeApp(environment.firebase),
    ],
    

    ng add コマンドで以下のコードが追加されます。今回は AngularFireModule を利用するのでこれは削除しておきます。

    provideFirebaseApp(() => initializeApp(environment.firebase)),
    provideAuth(() => getAuth()),
    provideFirestore(() => getFirestore()),
    

データベースの初期設定

Realtime Database または Cloud Firestore でデータベースを作成。今回は Firestore でテストモードで開始。

読み書きができるようにルールを変更しておく。

Angular側からFirebaseのデータを操作してみる

例として applicants というコレクションの中のドキュメントを表示、登録するためのコードを示す。

テスト用にデータを追加しておく。

一覧表示の実装

applicant.service.ts というサービスを作成したのでその中で一覧データを取得して返すようにする。 snapshotChanges() を使うことで observable として取得し map オペレーターを使って形を整えておく。 id は後ほど詳細画面の実装で使う。

import {
  AngularFirestore,
  AngularFirestoreCollection,
} from '@angular/fire/compat/firestore';
import { Observable } from 'rxjs';
import { Applicant } from './class/applicant';

export class ApplicantService {
	private applicantsCollection: AngularFirestoreCollection<Applicant>;
  applicants$: Observable<Applicant[]>;

  constructor(private afs: AngularFirestore) {
    this.applicantsCollection = afs.collection<Applicant>('applicants');
    this.applicants$ = this.applicantsCollection.snapshotChanges().pipe(
      map((actions) =>
        actions.map((a) => {
          const data = a.payload.doc.data() as Applicant;
          const id = a.payload.doc.id;
          return { id, ...data };
        })
      )
    );
  }

Firestoreから取得したコレクションをコンポーネント側で表示したいのでコンポーネント側の ts ファイルで DI(依存性の注入)を行う。

export class ListComponent implements OnInit {
  applicants$: Observable<Applicant[]>;

  constructor(private applicantService: ApplicantService) {
    this.applicants$ = this.applicantService.applicants$;
  }

html ファイルで | async を利用して以下のように書く。

<ul *ngFor="let applicant of applicants$ | async">
  <li>{{ applicant.name }}</li>
</ul>

これでブラウザを開くと、「イチロー」「ジロー」がリストで表示できる。

登録処理の実装

コンポーネント側の html にフォームを設置。ボタンをクリックした際にメソッドの実行とテキストエリアを空にするようにしておく。

<div class="mb-3">
  <label class="form-label">名前</label>
  <input type="text" [(ngModel)]="applicant.name" class="form-control" />
</div>

<button
  (click)="addApplicant(applicant); applicant.name = ''"
  type="button"
  class="btn btn-outline-info"
>
  送信
</button>

コンポーネント側の ts ファイルを編集。サービスに実装する登録用メソッドを呼び一覧ページにリダイレクトさせるようにする。

import { Router } from '@angular/router';
import { Applicant } from '../applicant';
import { ApplicantService } from '../applicant.service';

constructor(
    private applicantService: ApplicantService,
    private router: Router
  ) {}

  ngOnInit(): void {}

  addApplicant(applicant: Applicant): void {
    this.applicantService
      .addApplicant(applicant)
      .then(() => this.router.navigateByUrl('/list'));
  }

サービス側でFirestoreへの登録処理を実装。

import {
  AngularFirestore,
  AngularFirestoreCollection,
} from '@angular/fire/compat/firestore';
import { Applicant } from './class/applicant';

export class ApplicantService {
	private applicantsCollection: AngularFirestoreCollection<Applicant>;
	(略)

	addApplicant(applicant: Applicant) {
    return this.applicantsCollection.add(applicant);
  }

これでフォームから入力した値がFirestoreに登録できる。

詳細表示の実装

一覧表示の項目にリンクを貼る。

<ul *ngFor="let applicant of applicants$ | async">
  <li>
    <a routerLink="/detail/{{ applicant.id }}">
      {{ applicant.name }}
    </a>
  </li>
</ul>

リンクのURLは http://localhost:4200/detail/mq0h4o49rNexxxxxxx のような形式になる。

コンポーネントを作成しルーティングは以下の様に設定する。

const routes: Routes = [
  { path: '', component: FormComponent },
  { path: 'list', component: ListComponent },
  { path: 'detail/:id', component: DetailComponent },  👈 追記
];

コンポーネント側の ts ファイルで詳細データにアクセスしメンバ変数に代入しておく。

import { Observable } from 'rxjs';
import {
  AngularFirestore,
  AngularFirestoreDocument,
} from '@angular/fire/compat/firestore';
import { ActivatedRoute } from '@angular/router';
import { Router } from '@angular/router';
import { Applicant } from '../class/applicant';

export class DetailComponent implements OnInit {
  private id: string;
  private applicantDoc: AngularFirestoreDocument<Applicant>;
  applicant$: Observable<Applicant>;

  constructor(
    private route: ActivatedRoute,
    private afs: AngularFirestore,
    private router: Router
  ) {}

  ngOnInit(): void {
    this.id = `${this.route.snapshot.paramMap.get('id')}`;
    this.applicantDoc = this.afs.doc<Applicant>(`applicants/${this.id}`);
    this.applicant$ = this.applicantDoc.valueChanges();
  }

html ファイルでデータベースの値をフォームにセット。

<ng-container *ngIf="applicant$ | async as applicant">
  <div class="mb-3">
    <label class="form-label">名前</label>
    <input type="text" [(ngModel)]="applicant.name" class="form-control" />
  </div>

値が表示できる。

更新処理の実装

詳細表示のコンポーネントの ts 側で更新用のメソッドを追加。

updateApplicant(applicant: Applicant): void {
  this.applicantDoc.update(applicant);
}

html 側でボタンを設置。

<button routerLink="/list" class="btn btn-outline-secondary">戻る</button>
<button
  (click)="updateApplicant(applicant)"
  type="button"
  class="btn btn-outline-info"
>
  更新
</button>

更新を押して Firestore の画面を確認。

更新できた。

削除処理の実装

削除メソッドを ts 側に設置。

deleteApplicant(): void {
  if (window.confirm('削除しますか?')) {
    this.applicantDoc.delete().then(() => this.router.navigateByUrl('/list'));
  }
}

削除ボタンを設置。

<button (click)="deleteApplicant()" class="btn btn-outline-warning ms-auto">
  削除
</button>

削除を押すとダイアログを表示。

OKで削除ができる。

まとめ

Angular アプリと Firebase を連携させてデータベースでCRUD処理を行うための手順についてまとめました。

基本的な処理ですがWebアプリ開発では必須になるので参考になれば幸いです。

参考

AnglarFire公式サイト > Collections in AngualrFirestore

Discussion