尝试使用Angular操作Firebase实时数据库
首先
这次我想尝试一下Angular,上次用过React。
由于Firebase的设置与上次相同,因此本次我们将从创建项目开始。
(有关Firebase设置的信息,请参考上次的记录。)
太长不看。
我写的这段代码
逐步实施
创建项目
让我们使用Angular CLI开始吧。既然有机会,我们可以尝试一下从v10开始新增的严格模式。
$ npx @angular/cli new realtime-db-sample-with-angular --strict
安装库
由于提供了公式库[AngularFire](https://github.com/angular/angularfire),因此请安装该库。
在实现登录功能时,需要使用SDK,所以也要安装该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 { }
创建一个阅读写作服务
创建一个用于执行CRUD操作的服务。
在参考官方示例的基础上进行。
首先从CLI中生成。
$ npm run ng -- g service firebse-usecase --project=firebase
由于基本上与使用React时相同,我们将继续进行CRUD操作,因此这次决定只实现全部获取、注册和删除。
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。
如果能够获取到用户信息,则显示登出按钮。
如果无法获取到用户信息,则显示登录按钮。
<ng-container *ngIf="user | async; else showLoginButton">
<button (click)="logout()">ログアウト</button>
</ng-container>
<ng-template #showLoginButton>
<button (click)="login()">ログイン</button>
</ng-template>
在 app.module 中添加 */
将文本追加到 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)创建组件开始。
$ npm run ng -- g component components/list
以下是TS端的具体步骤:
使用创建的库进行全面获取→将结果显示在组件中。
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>
创建注册和删除页面
开始快速制作组件。
$ npm run ng -- g component components/form
我会使用响应式表单创建输入注册数据的部分。关于响应式表单,我之前有个笔记可以作为参考。
Angular的反应式表单综述-水无瀬的编程日记
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。
如果显示出像图片一样的登录按钮、注册表单和删除按钮,则可以。

总结
这次我尝试使用Angular来使用RealtimeDatabase。
由于官方提供了库,我觉得它相当容易实现。
由于我匆忙制作,对页面的分割等处理不太好,所以觉得再好好制作一下可能会更好。
因为公式库的帮助,使得很容易上手,所以我希望将来能够创造出一些东西。
参考链接
-
- angular/angularfire: The official Angular library for Firebase.
-
- angularfire/install-and-setup.md at master · angular/angularfire
-
- angularfire/objects.md at master · angular/angularfire
- angularfire/getting-started.md at master · angular/angularfire