使用 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 重新加载数据。 最重要的是,了解您的应用程序的要求以选择更好的实施方法始终很重要。