使用 Firebase 在 Angular 中進行狀態管理

已發表: 2022-03-11

狀態管理是開發 Web 應用程序時要考慮的一個非常重要的架構。

在本教程中,我們將介紹一種在使用 Firebase 作為其後端的 Angular 應用程序中管理狀態的簡單方法。

我們將討論一些概念,例如狀態、存儲和服務。 希望這將幫助您更好地掌握這些術語,並更好地理解其他狀態管理庫,例如 NgRx 和 NgXs。

我們將構建一個員工管理頁面,以涵蓋一些不同的狀態管理場景以及可以處理它們的方法。

Angular 中的組件、服務、Firestore 和狀態管理

在典型的 Angular 應用程序中,我們有組件和服務。 通常,組件將用作視圖模板。 服務將包含業務邏輯和/或與外部 API 或其他服務通信以完成操作或檢索數據。

組件連接到服務,服務連接到其他服務或 HTTP API。

組件通常會顯示數據並允許用戶與應用程序交互以執行操作。 執行此操作時,數據可能會更改,並且應用程序會通過更新視圖來反映這些更改。

Angular 的更改檢測引擎負責檢查綁定到視圖的組件中的值何時發生更改並相應地更新視圖。

隨著應用程序的增長,我們將開始擁有越來越多的組件和服務。 通常,了解數據如何變化並跟踪發生變化的位置可能會很棘手。

Angular 和 Firebase

當我們使用 Firebase 作為後端時,我們會獲得一個非常簡潔的 API,其中包含構建實時應用程序所需的大部分操作和功能。

@angular/fire是官方的 Angular Firebase 庫。 它是 Firebase JavaScript SDK 庫之上的一個層,可簡化 Firebase SDK 在 Angular 應用中的使用。 它非常適合 Angular 的良好實踐,例如使用 Observables 從 Firebase 獲取數據並將其顯示到我們的組件。

組件通過 @angular/fire 使用 Observables 訂閱 Firebase JavaScript API。

商店和狀態

我們可以將“狀態”視為應用程序中任何給定時間點顯示的值。 商店只是該應用程序狀態的持有者。

狀態可以建模為單個普通對像或一系列對象,反映應用程序的值。

一個存儲狀態,它有一個示例對象,其中包含一些簡單的名稱、城市和國家/地區鍵值對。

Angular/Firebase 示例應用

讓我們構建它:首先,我們將使用 Angular CLI 創建一個基本的應用程序腳手架,並將其與 Firebase 項目連接起來。

 $ npm install -g @angular/cli $ ng new employees-admin` Would you like to add Angular routing? Yes Which stylesheet format would you like to use? SCSS $ cd employees-admin/ $ npm install bootstrap # We'll add Bootstrap for the UI

而且,在styles.scss上:

 // ... @import "~bootstrap/scss/bootstrap";

接下來,我們將安裝@angular/fire

 npm install firebase @angular/fire

現在,我們將在 Firebase 控制台中創建一個 Firebase 項目。

Firebase 控制台的“添加項目”對話框。

然後我們準備創建一個 Firestore 數據庫。

對於本教程,我將從測試模式開始。 如果您計劃發佈到生產環境,您應該強制執行規則以禁止不適當的訪問。

“Cloud Firestore 的安全規則”對話框,選擇了“以測試模式啟動”而不是“以鎖定模式啟動”。

轉到項目概述 → 項目設置,然後將 Firebase 網絡配置複製到您的本地environments/environment.ts

新 Firebase 項目的空應用列表。

 export const environment = { production: false, firebase: { apiKey: "<api-key>", authDomain: "<auth-domain>", databaseURL: "<database-url>", projectId: "<project-id>", storageBucket: "<storage-bucket>", messagingSenderId: "<messaging-sender-id>" } };

至此,我們的應用程序的基本腳手架就位。 如果我們ng serve ,我們將得到:

Angular 腳手架,上面寫著“歡迎來到員工管理員!”

Firestore 和 Store 基類

我們將創建兩個通用抽像類,然後我們將對其進行類型化和擴展以構建我們的服務。

泛型允許您編寫沒有綁定類型的行為。 這增加了代碼的可重用性和靈活性。

通用 Firestore 服務

為了利用 TypeScript 泛型,我們要做的是為@angular/fire firestore服務創建一個基本的泛型包裝器。

讓我們創建app/core/services/firestore.service.ts

這是代碼:

 import { Inject } from "@angular/core"; import { AngularFirestore, QueryFn } from "@angular/fire/firestore"; import { Observable } from "rxjs"; import { tap } from "rxjs/operators"; import { environment } from "src/environments/environment"; export abstract class FirestoreService<T> { protected abstract basePath: string; constructor( @Inject(AngularFirestore) protected firestore: AngularFirestore, ) { } doc$(id: string): Observable<T> { return this.firestore.doc<T>(`${this.basePath}/${id}`).valueChanges().pipe( tap(r => { if (!environment.production) { console.groupCollapsed(`Firestore Streaming [${this.basePath}] [doc$] ${id}`) console.log(r) console.groupEnd() } }), ); } collection$(queryFn?: QueryFn): Observable<T[]> { return this.firestore.collection<T>(`${this.basePath}`, queryFn).valueChanges().pipe( tap(r => { if (!environment.production) { console.groupCollapsed(`Firestore Streaming [${this.basePath}] [collection$]`) console.table(r) console.groupEnd() } }), ); } create(value: T) { const id = this.firestore.createId(); return this.collection.doc(id).set(Object.assign({}, { id }, value)).then(_ => { if (!environment.production) { console.groupCollapsed(`Firestore Service [${this.basePath}] [create]`) console.log('[Id]', id, value) console.groupEnd() } }) } delete(id: string) { return this.collection.doc(id).delete().then(_ => { if (!environment.production) { console.groupCollapsed(`Firestore Service [${this.basePath}] [delete]`) console.log('[Id]', id) console.groupEnd() } }) } private get collection() { return this.firestore.collection(`${this.basePath}`); } }

這個abstract class將作為我們 Firestore 服務的通用包裝器。

這應該是我們應該注入AngularFirestore的唯一地方。 這將最大限度地減少@angular/fire庫更新時的影響。 此外,如果在某個時候我們想要更改庫,我們只需要更新這個類。

我添加了doc$collection$createdelete 。 它們包裝@angular/fire的方法並在 Firebase 流式傳輸數據時提供日誌記錄(這對於調試來說非常方便)以及在創建或刪除對象之後。

通用商店服務

我們的通用商店服務將使用 RxJS 的BehaviorSubject構建。 BehaviorSubject讓訂閱者在訂閱後立即獲得最後發出的值。 在我們的例子中,這很有幫助,因為我們可以在訂閱 store 時為所有組件使用初始值來開始 store。

store 將有兩種方法, patchset 。 (我們稍後會創建get方法。)

讓我們創建app/core/services/store.service.ts

 import { BehaviorSubject, Observable } from 'rxjs'; import { environment } from 'src/environments/environment'; export abstract class StoreService<T> { protected bs: BehaviorSubject<T>; state$: Observable<T>; state: T; previous: T; protected abstract store: string; constructor(initialValue: Partial<T>) { this.bs = new BehaviorSubject<T>(initialValue as T); this.state$ = this.bs.asObservable(); this.state = initialValue as T; this.state$.subscribe(s => { this.state = s }) } patch(newValue: Partial<T>, event: string = "Not specified") { this.previous = this.state const newState = Object.assign({}, this.state, newValue); if (!environment.production) { console.groupCollapsed(`[${this.store} store] [patch] [event: ${event}]`) console.log("change", newValue) console.log("prev", this.previous) console.log("next", newState) console.groupEnd() } this.bs.next(newState) } set(newValue: Partial<T>, event: string = "Not specified") { this.previous = this.state const newState = Object.assign({}, newValue) as T; if (!environment.production) { console.groupCollapsed(`[${this.store} store] [set] [event: ${event}]`) console.log("change", newValue) console.log("prev", this.previous) console.log("next", newState) console.groupEnd() } this.bs.next(newState) } }

作為一個泛型類,我們將推遲輸入,直到它被正確擴展。

構造函數將接收Partial<T>類型的初始值。 這將允許我們僅將值應用於狀態的某些屬性。 構造函數還將訂閱內部BehaviorSubject發射,並在每次更改後保持內部狀態更新。

patch()將接收Partial<T>類型的newValue並將其與 store 的當前this.state值合併。 最後,我們next() newState並將新狀態發送給所有的 store 訂閱者。

set()的工作方式非常相似,只是它不會修補狀態值,而是將其設置為它收到的newValue

我們將在更改發生時記錄狀態的前一個和下一個值,這將有助於我們調試和輕鬆跟踪狀態更改。

把它們放在一起

好的,讓我們看看這一切的實際效果。 我們要做的是創建一個員工頁面,其中將包含一個員工列表,以及一個用於添加新員工的表單。

讓我們更新app.component.html以添加一個簡單的導航欄:

 <nav class="navbar navbar-expand-lg navbar-light bg-light mb-3"> <span class="navbar-brand mb-0 h1">Angular + Firebase + State Management</span> <ul class="navbar-nav mr-auto"> <li class="nav-item" [routerLink]="['/employees']" routerLinkActive="active"> <a class="nav-link">Employees</a> </li> </ul> </nav> <router-outlet></router-outlet>

接下來,我們將創建一個核心模塊:

 ng gm Core

core/core.module.ts中,我們將添加應用所需的模塊:

 // ... import { AngularFireModule } from '@angular/fire' import { AngularFirestoreModule } from '@angular/fire/firestore' import { environment } from 'src/environments/environment'; import { ReactiveFormsModule } from '@angular/forms' @NgModule({ // ... imports: [ // ... AngularFireModule.initializeApp(environment.firebase), AngularFirestoreModule, ReactiveFormsModule, ], exports: [ CommonModule, AngularFireModule, AngularFirestoreModule, ReactiveFormsModule ] }) export class CoreModule { }

現在,讓我們創建員工頁面,從員工模塊開始:

 ng gm Employees --routing

employees-routing.module.ts中,讓我們添加employees路由:

 // ... import { EmployeesPageComponent } from './components/employees-page/employees-page.component'; // ... const routes: Routes = [ { path: 'employees', component: EmployeesPageComponent } ]; // ...

employees.module.ts中,我們將導入ReactiveFormsModule

 // ... import { ReactiveFormsModule } from '@angular/forms'; // ... @NgModule({ // ... imports: [ // ... ReactiveFormsModule ] }) export class EmployeesModule { }

現在,讓我們在app.module.ts文件中添加這兩個模塊:

 // ... import { EmployeesModule } from './employees/employees.module'; import { CoreModule } from './core/core.module'; imports: [ // ... CoreModule, EmployeesModule ],

最後,讓我們創建員工頁面的實際組件,以及相應的模型、服務、商店和狀態。

 ng gc employees/components/EmployeesPage ng gc employees/components/EmployeesList ng gc employees/components/EmployeesForm

對於我們的模型,我們需要一個名為models/employee.ts的文件:

 export interface Employee { id: string; name: string; location: string; hasDriverLicense: boolean; }

我們的服務將存在於一個名為employees/services/employee.firestore.ts的文件中。 該服務將擴展之前創建的通用FirestoreService<T> ,我們只需設置 Firestore 集合的basePath

 import { Injectable } from '@angular/core'; import { FirestoreService } from 'src/app/core/services/firestore.service'; import { Employee } from '../models/employee'; @Injectable({ providedIn: 'root' }) export class EmployeeFirestore extends FirestoreService<Employee> { protected basePath: string = 'employees'; }

然後我們將創建文件employees/states/employees-page.ts 。 這將作為員工頁面的狀態:

 import { Employee } from '../models/employee'; export interface EmployeesPage { loading: boolean; employees: Employee[]; formStatus: string; }

state 將有一個loading值,它決定是否在頁面上顯示加載消息, employees自己,以及一個formStatus變量來處理表單的狀態(例如SavingSaved 。)

我們需要一個位於employees/services/employees-page.store.ts的文件。 在這裡,我們將擴展之前創建的StoreService<T> 。 我們將設置商店名稱,用於在調試時識別它。

該服務將初始化並保存員工頁面的狀態。 請注意,構造函數使用頁面的初始狀態調用super() 。 在這種情況下,我們將使用loading=true和一個空的員工數組來初始化狀態。

 import { EmployeesPage } from '../states/employees-page'; import { StoreService } from 'src/app/core/services/store.service'; import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class EmployeesPageStore extends StoreService<EmployeesPage> { protected store: string = 'employees-page'; constructor() { super({ loading: true, employees: [], }) } }

現在讓我們創建EmployeesService來整合EmployeeFirestoreEmployeesPageStore

 ng gs employees/services/Employees

請注意,我們在此服務中註入了EmployeeFirestoreEmployeesPageStore 。 這意味著EmployeesService將包含並協調對Firestore 和商店的調用以更新狀態。 這將幫助我們創建一個供組件調用的 API。

 import { EmployeesPageStore } from './employees-page.store'; import { EmployeeFirestore } from './employee.firestore'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { Employee } from '../models/employee'; import { tap, map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class EmployeesService { constructor( private firestore: EmployeeFirestore, private store: EmployeesPageStore ) { this.firestore.collection$().pipe( tap(employees => { this.store.patch({ loading: false, employees, }, `employees collection subscription`) }) ).subscribe() } get employees$(): Observable<Employee[]> { return this.store.state$.pipe(map(state => state.loading ? [] : state.employees)) } get loading$(): Observable<boolean> { return this.store.state$.pipe(map(state => state.loading)) } get noResults$(): Observable<boolean> { return this.store.state$.pipe( map(state => { return !state.loading && state.employees && state.employees.length === 0 }) ) } get formStatus$(): Observable<string> { return this.store.state$.pipe(map(state => state.formStatus)) } create(employee: Employee) { this.store.patch({ loading: true, employees: [], formStatus: 'Saving...' }, "employee create") return this.firestore.create(employee).then(_ => { this.store.patch({ formStatus: 'Saved!' }, "employee create SUCCESS") setTimeout(() => this.store.patch({ formStatus: '' }, "employee create timeout reset formStatus"), 2000) }).catch(err => { this.store.patch({ loading: false, formStatus: 'An error ocurred' }, "employee create ERROR") }) } delete(id: string): any { this.store.patch({ loading: true, employees: [] }, "employee delete") return this.firestore.delete(id).catch(err => { this.store.patch({ loading: false, formStatus: 'An error ocurred' }, "employee delete ERROR") }) } }

讓我們看一下該服務將如何工作。

在構造函數中,我們將訂閱 Firestore 員工集合。 一旦 Firestore 從集合中發出數據,我們將更新存儲,設置loading=false並使用 Firestore 返回的集合來設置employees 。 由於我們注入了EmployeeFirestore ,因此從 Firestore 返回的對像類型為Employee ,從而啟用了更多 IntelliSense 功能。

此訂閱將在應用程序處於活動狀態時處於活動狀態,並在每次 Firestore 流式傳輸數據時偵聽所有更改並更新商店。

 this.firestore.collection$().pipe( tap(employees => { this.store.patch({ loading: false, employees, }, `employees collection subscription`) }) ).subscribe()

employees$()loading$()函數將選擇我們稍後要在組件上使用的狀態。 employees$()將在狀態加載時返回一個空數組。 這將允許我們在視圖上顯示正確的消息。

 get employees$(): Observable<Employee[]> { return this.store.state$.pipe(map(state => state.loading ? [] : state.employees)) } get loading$(): Observable<boolean> { return this.store.state$.pipe(map(state => state.loading)) }

好的,現在我們已經準備好所有的服務,我們可以構建我們的視圖組件了。 但在我們這樣做之前,快速復習可能會派上用場……

RxJs Observables 和async管道

Observables 允許訂閱者以流的形式接收數據的發射。 這與async管道相結合,可以非常強大。

async管道負責訂閱 Observable 並在發出新數據時更新視圖。 更重要的是,它會在組件被銷毀時自動取消訂閱,從而保護我們免受內存洩漏。

您可以在官方文檔中閱讀有關 Observables 和 RxJs 庫的更多信息。

創建視圖組件

employees/components/employees-page/employees-page.component.html中,我們將放置以下代碼:

 <div class="container"> <div class="row"> <div class="col-12 mb-3"> <h4> Employees </h4> </div> </div> <div class="row"> <div class="col-6"> <app-employees-list></app-employees-list> </div> <div class="col-6"> <app-employees-form></app-employees-form> </div> </div> </div>

同樣, employees/components/employees-list/employees-list.component.html也會有這個,使用上面提到的async管道技術:

 <div *ngIf="loading$ | async"> Loading... </div> <div *ngIf="noResults$ | async"> No results </div> <div class="card bg-light mb-3" *ngFor="let employee of employees$ | async"> <div class="card-header">{{employee.location}}</div> <div class="card-body"> <h5 class="card-title">{{employee.name}}</h5> <p class="card-text">{{employee.hasDriverLicense ? 'Can drive': ''}}</p> <button (click)="delete(employee)" class="btn btn-danger">Delete</button> </div> </div>

但在這種情況下,我們也需要一些用於組件的 TypeScript 代碼。 文件employees/components/employees-list/employees-list.component.ts將需要這個:

 import { Employee } from '../../models/employee'; import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { EmployeesService } from '../../services/employees.service'; @Component({ selector: 'app-employees-list', templateUrl: './employees-list.component.html', styleUrls: ['./employees-list.component.scss'] }) export class EmployeesListComponent implements OnInit { loading$: Observable<boolean>; employees$: Observable<Employee[]>; noResults$: Observable<boolean>; constructor( private employees: EmployeesService ) {} ngOnInit() { this.loading$ = this.employees.loading$; this.noResults$ = this.employees.noResults$; this.employees$ = this.employees.employees$; } delete(employee: Employee) { this.employees.delete(employee.id); } }

因此,轉到瀏覽器,我們現在將擁有的是:

一個空的員工列表,以及“員工表格有效”的消息!

控制台將有以下輸出:

補丁事件顯示前後值的變化。

看看這個,我們可以看出 Firestore 使用空值流式傳輸employees集合,並且employees-page存儲已修補,將loadingtrue設置為false

好的,讓我們構建將新員工添加到 Firestore 的表單:

員工表格

employees/components/employees-form/employees-form.component.html中,我們將添加以下代碼:

 <form [formGroup]="form" (ngSubmit)="submit()"> <div class="form-group"> <label for="name">Name</label> <input type="string" class="form-control" formControlName="name" [class.is-invalid]="isInvalid('name')"> <div class="invalid-feedback"> Please enter a Name. </div> </div> <div class="form-group"> <select class="custom-select" formControlName="location" [class.is-invalid]="isInvalid('location')"> <option value="" selected>Choose location</option> <option *ngFor="let loc of locations" [ngValue]="loc">{{loc}}</option> </select> <div class="invalid-feedback"> Please select a Location. </div> </div> <div class="form-group form-check"> <input type="checkbox" class="form-check-input" formControlName="hasDriverLicense"> <label class="form-check-label" for="hasDriverLicense">Has driver license</label> </div> <button [disabled]="form.invalid" type="submit" class="btn btn-primary d-inline">Add</button> <span class="ml-2">{{ status$ | async }}</span> </form>

相應的 TypeScript 代碼將存在於employees/components/employees-form/employees-form.component.ts

 import { EmployeesService } from './../../services/employees.service'; import { AngularFirestore } from '@angular/fire/firestore'; import { Component, OnInit } from '@angular/core'; import { FormGroup, FormControl, Validators } from '@angular/forms'; import { Observable } from 'rxjs'; @Component({ selector: 'app-employees-form', templateUrl: './employees-form.component.html', styleUrls: ['./employees-form.component.scss'] }) export class EmployeesFormComponent implements OnInit { form: FormGroup = new FormGroup({ name: new FormControl('', Validators.required), location: new FormControl('', Validators.required), hasDriverLicense: new FormControl(false) }); locations = [ 'Rosario', 'Buenos Aires', 'Bariloche' ] status$: Observable < string > ; constructor( private employees: EmployeesService ) {} ngOnInit() { this.status$ = this.employees.formStatus$; } isInvalid(name) { return this.form.controls[name].invalid && (this.form.controls[name].dirty || this.form.controls[name].touched) } async submit() { this.form.disable() await this.employees.create({ ...this.form.value }) this.form.reset() this.form.enable() } }

該表單將調用EmployeesServicecreate()方法。 現在頁面看起來是這樣的:

與以前相同的空員工列表,這次是用於添加新員工的表單。

讓我們看看當我們添加一個新員工時會發生什麼。

添加新員工

添加新員工後,我們將在控制台看到以下獲取輸出:

補丁事件與 Firestore 事件混合,編號為 1 到 6(本地創建、Firestore 集合流式傳輸、本地集合訂閱、Firestore 創建、本地創建成功和本地創建超時表單狀態重置。)

這些都是添加新員工時觸發的所有事件。 讓我們仔細看看。

當我們調用create()時,我們將執行以下代碼,將loading=trueformStatus='Saving...'employees數組設置為空(上圖中的(1) )。

 this.store.patch({ loading: true, employees: [], formStatus: 'Saving...' }, "employee create") return this.firestore.create(employee).then(_ => { this.store.patch({ formStatus: 'Saved!' }, "employee create SUCCESS") setTimeout(() => this.store.patch({ formStatus: '' }, "employee create timeout reset formStatus"), 2000) }).catch(err => { this.store.patch({ loading: false, formStatus: 'An error ocurred' }, "employee create ERROR") })

接下來,我們調用基本 Firestore 服務來創建員工,該員工記錄(4) 。 在 promise 回調中,我們設置formStatus='Saved!' 和日誌(5) 。 最後,我們設置了一個超時時間,將formStatus設置回空,記錄(6)

日誌事件(2)(3)是 Firestore 訂閱員工集合觸發的事件。 當EmployeesService被實例化時,我們訂閱該集合併在每次發生更改時接收該集合。

通過將employees數組設置為來自Firestore 的員工,這將為store 設置一個新狀態, loading=false

如果我們展開日誌組,我們將看到每個事件和存儲更新的詳細數據,包括前一個值和下一個值,這對於調試很有用。

擴展了包含所有狀態管理詳細信息的先前日誌輸出。

這是添加新員工後頁面的樣子:

裡面有一張員工卡的員工名單,表格還是從添加到填寫的。

添加摘要組件

假設我們現在想在我們的頁面上顯示一些摘要數據。 假設我們想要員工總數,有多少是司機,有多少來自羅薩里奧。

我們首先將新的狀態屬性添加到employees/states/employees-page.ts中的頁面狀態模型中:

 // ... export interface EmployeesPage { loading: boolean; employees: Employee[]; formStatus: string; totalEmployees: number; totalDrivers: number; totalRosarioEmployees: number; }

我們將在employees/services/emplyees-page.store.ts的 store 中初始化它們:

 // ... constructor() { super({ loading: true, employees: [], totalDrivers: 0, totalEmployees: 0, totalRosarioEmployees: 0 }) } // ...

接下來,我們將計算新屬性的值並將它們各自的選擇器添加到EmployeesService中:

 // ... this.firestore.collection$().pipe( tap(employees => { this.store.patch({ loading: false, employees, totalEmployees: employees.length, totalDrivers: employees.filter(employee => employee.hasDriverLicense).length, totalRosarioEmployees: employees.filter(employee => employee.location === 'Rosario').length, }, `employees collection subscription`) }) ).subscribe() // ... get totalEmployees$(): Observable < number > { return this.store.state$.pipe(map(state => state.totalEmployees)) } get totalDrivers$(): Observable < number > { return this.store.state$.pipe(map(state => state.totalDrivers)) } get totalRosarioEmployees$(): Observable < number > { return this.store.state$.pipe(map(state => state.totalRosarioEmployees)) } // ...

現在,讓我們創建摘要組件:

 ng gc employees/components/EmployeesSummary

我們將把它放在employees/components/employees-summary/employees-summary.html

 <p> <span class="font-weight-bold">Total:</span> {{total$ | async}} <br> <span class="font-weight-bold">Drivers:</span> {{drivers$ | async}} <br> <span class="font-weight-bold">Rosario:</span> {{rosario$ | async}} <br> </p>

employees/components/employees-summary/employees-summary.ts

 import { Component, OnInit } from '@angular/core'; import { EmployeesService } from '../../services/employees.service'; import { Observable } from 'rxjs'; @Component({ selector: 'app-employees-summary', templateUrl: './employees-summary.component.html', styleUrls: ['./employees-summary.component.scss'] }) export class EmployeesSummaryComponent implements OnInit { total$: Observable < number > ; drivers$: Observable < number > ; rosario$: Observable < number > ; constructor( private employees: EmployeesService ) {} ngOnInit() { this.total$ = this.employees.totalEmployees$; this.drivers$ = this.employees.totalDrivers$; this.rosario$ = this.employees.totalRosarioEmployees$; } }

然後我們將該組件添加到employees/employees-page/employees-page.component.html

 // ... <div class="col-12 mb-3"> <h4> Employees </h4> <app-employees-summary></app-employees-summary> </div> // ...

結果如下:

員工頁面,現在列表上方有一個摘要,顯示員工總數、司機人數和來自羅薩里奧的人數。

在控制台中,我們有:

控制台輸出顯示更改匯總值的補丁事件。

員工服務計算每次排放的總totalEmployeestotalDriverstotalRosarioEmployees並更新狀態。

本教程的完整代碼可在 GitHub 上找到,並且還有一個現場演示。

使用 Observables 管理 Angular 應用程序狀態……檢查!

在本教程中,我們介紹了一種使用 Firebase 後端管理 Angular 應用程序狀態的簡單方法。

這種方法非常適合使用 Observables 的 Angular 指南。 它還通過跟踪應用程序狀態的所有更新來促進調試。

通用商店服務還可用於管理不使用 Firebase 功能的應用的狀態,或者僅管理應用的數據或來自其他 API 的數據。

但是在你不加選擇地應用它之前,需要考慮的一件事是, EmployeesService在構造函數上訂閱了 Firestore,並在應用程序處於活動狀態時繼續監聽。 如果我們在應用程序的多個頁面上使用員工列表,這可能很有用,以避免在頁面之間導航時從 Firestore 獲取數據。

但這在其他情況下可能不是最佳選擇,例如您只需提取一次初始值,然後手動觸發從 Firebase 重新加載數據。 最重要的是,了解您的應用程序的要求以選擇更好的實施方法始終很重要。