はじめに
前回Reactで使ってみたので今回はAngularでやってみる。
Firebaseの設定は前回と同じため、今回はプロジェクト作るところから始めていく。
(Firebaseの設定については前回参照)
TL;DR.
実装していく
プロジェクト作成
angular cliからスタート。
せっかくなのでv10から追加されたstrictモードを使ってみる。
$ npx @angular/cli new realtime-db-sample-with-angular --strict
ライブラリのインストール
公式から提供されているライブラリであるAngularFire
があるのでそちらをインストールする。
ログイン周りを実装するときにSDKが必要になるので、こちらもインストールしておく。
$ npm run ng -- add @angular/fire
$ npm install --save firebase
Firebase周り
Firebaseの操作に関する部分は表示系とは直接関係無い+汎用的なものになると思うのでライブラリ化しておく。
$ npm run ng -- g library firebase-library
初期化処理
Quick Start を参考に初期化処理周りをサクッと追加する。
コンフィグ周りを個別のファイルに切り出して、
import {FirebaseOptions} from '@angular/fire';
export const firebaseConfig: FirebaseOptions = {
production: false,
firebase: {
// コピペ
}
};
それをfirebase-library.module.ts
で初期化のときに読み込む。
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {AngularFireModule} from '@angular/fire';
import {AngularFireDatabaseModule} from '@angular/fire/database';
import {FirebaseUsecaseService} from './service/firebase-usecase.service';
// ↑で作ったconfigファイル
import {firebaseConfig} from './config/config';
import {LoginComponent} from './components/login/login.component';
import {AngularFireAuthModule} from '@angular/fire/auth';
import {FirebaseFormatterService} from './service/firebase-formatter.service';
@NgModule({
declarations: [],
imports: [
// 初期化のときにconfigを渡してあげる
AngularFireModule.initializeApp(firebaseConfig.firebase),
// RealtimeDatabaseを使うので必要なmoduleをimport
AngularFireDatabaseModule,
// ログイン周りに必要なmoduleをimport
AngularFireAuthModule
],
providers: [],
exports: []
})
export class FirebaseLibraryModule {
}
読み書きするserviceを作成
CRUD周りの操作を行うサービスを作成。
こちらも公式サンプル を参考にしながら進めていく。
まずはCLIで生成するところから。
$ npm run ng -- g service firebse-usecase --project=firebase
CRUDを進めていくが基本的にはReactでやった時と同じため、
今回は全件取得、登録、削除のみを実装することにする。
import {Injectable} from '@angular/core';
import {AngularFireDatabase, SnapshotAction} from '@angular/fire/database';
import {Observable} from 'rxjs';
import {map, take} from 'rxjs/operators';
import {FirebaseFormatterService} from './firebase-formatter.service';
import {FirebaseKeyValue} from '../types/firebase-types';
@Injectable({
providedIn: 'root'
})
export class FirebaseUsecaseService {
private items: Observable<SnapshotAction<string>[]>;
constructor(private readonly db: AngularFireDatabase, private readonly formatter: FirebaseFormatterService) {
// 指定したURL以下の値がリスト形式で取得される
// 絞りたければ渡すパスを絞っていけば良い
this.items = this.db.list<string>('/sample').snapshotChanges();
}
/**
* 全件取得する
*/
fetchDocumentAll() {
// 余分なパラメータが多いので、使いやすい形式に整形する
return this.items.pipe(map(this.documentToResponse));
}
/**
* 登録を行う
*/
async setDocument(registerKeyValue: FirebaseKeyValue) {
// setでの更新は上書きになってしまうので、登録済みのデータを取得しておく
// 今回は値を取得してから後続処理に移りたいため、Promiseに変換して取得を待つ
const item = await this.fetchDocumentAll().pipe(take(1)).toPromise();
// 登録したいデータと登録されているデータをマージしつつ整形する
const registerDocument = this.objectToDocument([...item, registerKeyValue]);
// sampleのデータを上書き登録
this.db.object('/sample').set(registerDocument);
}
/**
* 指定したパス配下のデータを全て削除する
*/
deleteAll() {
this.db.object('/sample').remove();
}
/**
* 取得したデータからkey,valueの値のみを取り出す
*/
private documentToResponse(document: SnapshotAction<string>[]): FirebaseKeyValue[] {
return document.map(item => ({key: item.key, value: item.payload.val()}));
}
/**
* 登録用のデータに整形する
*/
private objectToDocument(keyValues: FirebaseKeyValue[]): FirebaseDocument {
// 登録したいデータは{[key: string]: value}形式なので整形
return keyValues.reduce((previous, current) => {
// keyが無い場合は今回考慮しない
const registereData = {[current.key!]: current.value};
return {...previous, ...registereData};
}, {});
}
}
ログインコンポーネント
登録、削除はログイン済みの場合のみ可能としているので、ログイン処理を実装していく。
ログイン処理を行うコンポーネントは作成したライブラリ側に用意する。
$ npm run ng -- g component components/login --project=firebase-library
こちらも公式のサンプルを参考にしながら進めていく。
まずはts側から。
import {Component} from '@angular/core';
import {AngularFireAuth} from '@angular/fire/auth';
import {Observable} from 'rxjs';
import {auth, User} from 'firebase/app';
@Component({
selector: 'lib-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent {
private _user: Observable<User | null>
constructor(private auth: AngularFireAuth) {
this._user = this.auth.user;
}
login() {
// Googleログインを行う
this.auth.signInWithPopup(new auth.GoogleAuthProvider());
}
logout() {
// ログアウト
this.auth.signOut();
}
get user() {
return this._user;
}
}
次にHTML側を書いていく。
user情報が取得できていれば、ログアウトボタンを表示。
できていなければログインボタンを表示する。
<ng-container *ngIf="user | async; else showLoginButton">
<button (click)="logout()">ログアウト</button>
</ng-container>
<ng-template #showLoginButton>
<button (click)="login()">ログイン</button>
</ng-template>
app.moduleに追記
ここまでで作ったlibraryを使えるようにapp.module
に追記する。
import {ReactiveFormsModule} from '@angular/forms';
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {ListComponent} from './components/list/list.component';
import {FirebaseLibraryModule} from 'firebase-library';
import {FormComponent} from './components/form/form.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
// これを追加する
FirebaseLibraryModule,
// ルーティングを使いたいのでimportしておく
AppRoutingModule,
// 後でリアクティブフォームを使いたいので、importしておく
ReactiveFormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
}
取得結果表示ページ作成
ここから表示系を準備していく。
まずは取得結果を表示するページを作成する。
CLIでcomponentを作るところから。
$ npm run ng -- g component components/list
ts側は下記の通り。
作ったlibraryを使い全件取得→結果を表示するだけのcomponentにする。
import {Component} from '@angular/core';
import {FirebaseUsecaseService} from 'firebase-library';
import {BehaviorSubject} from 'rxjs';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.scss']
})
export class ListComponent {
private items$ = new BehaviorSubject<any[]>([]);
constructor(private readonly firebase: FirebaseUsecaseService) {
firebase.fetchDocumentAll().subscribe(res => {
this.items$.next(res);
}, err => {
console.log('error', err);
});
}
get items() {
return this.items$;
}
}
HTML側は↓の通り。
itemsはBehaviorSubject
なので、asyncパイプを使って表示させてあげる。
<dl>
<ng-container *ngFor="let item of items | async">
<dt>key: {{item.key}}</dt>
<dt>value: {{item.value}}</dt>
</ng-container>
</dl>
登録、削除ページ作成
サクッとcomponentを作るところから始める。
$ npm run ng -- g component components/form
登録するデータを入力する箇所はリアクティブフォームで作ってみる。
リアクティブフォームについては昔メモしたのでそちらを参考に。
import {FormGroup, FormControl, Validators} from '@angular/forms';
import {Component} from '@angular/core';
import {FirebaseUsecaseService} from 'firebase-library';
@Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})
export class FormComponent {
// key、valueはどちらも必須とする
readonly formGroup = new FormGroup({
key: new FormControl('', [Validators.required]),
value: new FormControl('', [Validators.required])
});
constructor(private readonly firebase: FirebaseUsecaseService) {
}
registerData() {
// setDocumentはasync functionだけど、今回は待つ必要が無いので呼び出しっぱなし
this.firebase.setDocument({key: this.key?.value, value: this.value?.value});
}
deleteAll() {
this.firebase.deleteAll();
}
get key() {
return this.formGroup.get('key');
}
get value() {
return this.formGroup.get('value');
}
}
HTML側は下記の通り。
ここでログインボタンを読み込んでおく。
<div>
<!-- 作成したログイン/ログアウトボタンコンポーネント -->
<lib-login></lib-login>
</div>
<form [formGroup]="formGroup">
<label>key: <input type="text" formControlName="key"/></label>
<!-- 必須項目未入力のときのエラーメッセージ -->
<div *ngIf="key?.invalid && (key?.dirty || key?.touched)">
<span *ngIf="key?.hasError('required')">必須です。</span>
</div>
<label>value: <input type="text" formControlName="value"/></label>
<!-- 必須項目未入力のときのエラーメッセージ -->
<div *ngIf="value?.invalid && (value?.dirty || value?.touched)">
<span *ngIf="value?.hasError('required')">必須です。</span>
</div>
<button (click)="registerData()">登録</button>
</form>
<button (click)="deleteAll()">全消し</button>
ルーティングの設定
一覧表示と登録、削除を一応ページ分ける。
ルーティングの設定が必要なのでサクッと実装しておく。
import {NgModule} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {ListComponent} from './components/list/list.component';
import {FormComponent} from './components/form/form.component';
const routes: Routes = [
{path: 'list', component: ListComponent},
{path: 'form', component: FormComponent}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}
動作確認
実装が終わったので動作確認していく。
先にライブラリのビルドが必要なのでまずはそこから。
$ npm run build -- firebase-library
ビルドが無事成功したらアプリを起動する。
$ npm start
起動できたらhttp://localhost:4200/list
にアクセスしてみる。
画像の取得結果を表示できていればOK。
次にhttp://localhost:4200/form
にアクセスしてみる。
画像の様にログインボタン、登録フォーム、削除ボタンが表示されていればOK。
まとめ
今回はAngularでRealtimeDatabaseを使ってみた。
公式からライブラリが提供されているのもあり、結構簡単に実装できたと思う。
さっくり作りすぎてページ分けとか微妙だったので、もうちょっときれいに作っても良かった気がする。