本文概述
- Angular中的组件, 服务, Firestore和状态管理
- Angular / Firebase示例应用程序
- Firestore和商店基类
- 放在一起
- 创建视图组件
- 员工表格
- 使用Observables管理Angular应用程序状态…检查!
在本教程中, 我们将介绍一种简单的方法来管理以Firebase为后端的Angular应用程序中的状态。
我们将介绍一些概念, 例如州, 商店和服务。希望这将帮助你更好地理解这些术语, 并更好地理解其他状态管理库, 例如NgRx和NgXs。
我们将建立一个员工管理页面, 以涵盖一些不同的状态管理方案以及可以解决这些方案的方法。
Angular中的组件, 服务, Firestore和状态管理 在典型的Angular应用程序中, 我们具有组件和服务。通常, 组件将用作视图模板。服务将包含业务逻辑和/或与外部API或其他服务进行通信以完成操作或检索数据。
文章图片
组件通常会显示数据, 并允许用户与应用交互以执行操作。在执行此操作时, 数据可能会更改, 并且应用程序会通过更新视图来反映这些更改。
Angular的更改检测引擎负责检查绑定到视图的组件中的值何时发生更改, 并相应地更新视图。
随着应用的发展, 我们将开始拥有越来越多的组件和服务。通常, 了解数据如何变化并跟踪发生的位置可能很棘手。
Angular和Firebase
当我们使用Firebase作为后端时, 我们将获得一个真正简洁的API, 其中包含构建实时应用程序所需的大多数操作和功能。
@ angular / fire是官方的Angular Firebase库。该层位于Firebase JavaScript SDK库的顶层, 可简化Angular应用中Firebase SDK的使用。它非常适合Angular的良好做法, 例如使用Observables从Firebase获取和显示数据到我们的组件。
文章图片
商店和州
我们可以将” 状态” 视为在应用程序中任何给定时间点显示的值。商店只是该应用程序状态的持有者。
可以将状态建模为单个普通对象或一系列对象, 以反映应用程序的值。
文章图片
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项目。
文章图片
然后, 我们准备创建Firestore数据库。
在本教程中, 我将从测试模式开始。如果你打算发布产品, 则应执行规则以禁止不当访问。
文章图片
转到项目概述→项目设置, 然后将Firebase Web配置复制到本地环境/environment.ts。
文章图片
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>
"
}
};
至此, 我们已经为我们的应用准备了基本的支架。如果我们提供服务, 我们将获得:
文章图片
Firestore和商店基类 我们将创建两个通用抽象类, 然后将其键入并扩展以构建我们的服务。
泛型允许你编写没有绑定类型的行为。这为你的代码增加了可重用性和灵活性。
通用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}`);
}
}
此抽象类将用作Firestore服务的通用包装。
这应该是我们唯一应注入AngularFirestore的地方。当@ angular / fire库更新时, 这将使影响最小化。此外, 如果在某个时候我们想更改库, 则只需要更新此类。
我添加了doc $, collection $, create和delete。它们包装@ angular / fire的方法, 并在Firebase流数据时提供日志记录-这对于调试非常方便-在创建或删除对象之后。
通用商店服务
我们的通用商店服务将使用RxJS的BehaviorSubject构建。 BehaviorSubject允许订户在订阅后立即获得最后发出的值。对于我们来说, 这很有用, 因为当我们的所有组件订阅商店时, 我们都可以使用初始值开始商店。
商店将有两种方法, 补丁和设置。 (我们稍后将创建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并将其与商店的当前this.state值合并。最后, 我们next()newState并将新状态发送给所有商店订户。
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 g m 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 { }
【使用Firebase在Angular中进行状态管理】现在, 从” 雇员” 模块开始创建” 雇员” 页面:
ng g m Employees --routing
在employee-routing.module.ts中, 添加员工路线:
// ...
import { EmployeesPageComponent } from './components/employees-page/employees-page.component';
// ...
const routes: Routes = [
{ path: 'employees', component: EmployeesPageComponent }
];
// ...
然后在employee.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 g c employees/components/EmployeesPage
ng g c employees/components/EmployeesList
ng g c employees/components/EmployeesForm
对于我们的模型, 我们需要一个名为models / employee.ts的文件:
export interface Employee {
id: string;
name: string;
location: string;
hasDriverLicense: boolean;
}
我们的服务将保存在名为employee / 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';
}
然后, 我们将创建文件employee / states / employees-page.ts。这将用作雇员页面的状态:
import { Employee } from '../models/employee';
export interface EmployeesPage {loading: boolean;
employees: Employee[];
formStatus: string;
}
该状态将具有一个加载值, 该值确定是否在页面上显示加载消息, 员工本身以及一个用于处理表单状态(例如保存或已保存)的formStatus变量。
我们需要一个位于employee / 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来集成EmployeeFirestore和EmployeesPageStore:
ng g s employees/services/Employees
请注意, 我们正在此服务中注入EmployeeFirestore和EmployeesPageStore。这意味着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返回的集合并将其设置为员工。由于我们已注入EmployeeFirestore, 因此将从Firestore返回的对象键入Employee, 这将启用更多IntelliSense功能。
当应用程序处于活动状态时, 此订阅将一直有效, 每次Firestore流数据时, 侦听所有更改并更新商店。
this.firestore.collection$().pipe(
tap(employees =>
{
this.store.patch({
loading: false, employees, }, `employees collection subscription`)
})
).subscribe()
employee $()和loading $()函数将选择我们想要稍后在组件上使用的状态。当状态加载时, employee $()将返回一个空数组。这将使我们能够在视图上显示正确的消息。
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和异步管道
观测值允许订户以流的形式接收数据的发射。与异步管道结合使用, 可以非常强大。
异步管道负责订阅Observable并在发出新数据时更新视图。更重要的是, 当组件被销毁时, 它将自动退订, 从而保护我们免受内存泄漏的影响。
你可以在官方文档中大致了解有关Observables和RxJs库的更多信息。
创建视图组件 在employee / 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将使用上述异步管道技术进行操作:
<
div *ngIf="loading$ | async">
Loading...
<
/div>
<
div *ngIf="noResults$ | async">
No results
<
/div>
<
div class="card bg-light mb-3" style="max-width: 18rem;
" *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代码。文件employee / 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存储被修补, 将加载从true设置为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" id="name"
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" id="hasDriverLicense"
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代码将存在于employee / 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()
}}
该表单将调用EmployeesService的create()方法。现在页面看起来像这样:
文章图片
让我们看看添加新员工时会发生什么。
添加新员工
添加新员工后, 我们将看到以下内容输出到控制台:
文章图片
这些都是添加新员工时触发的所有事件。让我们仔细看看。
当我们调用create()时, 我们将执行以下代码, 将loading = true, formStatus =’ 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订阅employee集合触发的事件。实例化EmployeesService时, 我们订阅该集合, 并在每次发生更改时接收该集合。
通过将employees数组设置为来自Firestore的雇员, 这将为loading = false的商店设置一个新状态。
如果我们扩展日志组, 则会看到每个事件和商店更新的详细数据, 以及上一个值和下一个值, 这对于调试非常有用。
文章图片
这是添加新员工后页面的外观:
文章图片
添加摘要组件
假设我们现在要在页面上显示一些摘要数据。假设我们需要员工总数, 司机人数以及来自Rosario的人数。
首先, 将新的状态属性添加到employee / states / employees-page.ts中的页面状态模型中:
// ...
export interface EmployeesPage {loading: boolean;
employees: Employee[];
formStatus: string;
totalEmployees: number;
totalDrivers: number;
totalRosarioEmployees: number;
}
然后, 我们将在员工/服务/emplyees-page.store.ts中的商店中对其进行初始化:
// ...
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 g c employees/components/EmployeesSummary
我们将其放在employee / 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-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$;
}}
然后, 我们将组件添加到employee / employees-page / employees-page.component.html:
// ...
<
div class="col-12 mb-3">
<
h4>
Employees
<
/h4>
<
app-employees-summary>
<
/app-employees-summary>
<
/div>
// ...
结果如下:
文章图片
在控制台中, 我们有:
文章图片
员工服务计算每个排放的total totalEmployees, totalDrivers和totalRosarioEmployees并更新状态。
本教程的完整代码可在GitHub上找到, 并且还有一个实时演示。
使用Observables管理Angular应用程序状态…检查! 在本教程中, 我们介绍了一种使用Firebase后端在Angular应用程序中管理状态的简单方法。
这种方法非常适合使用Observables的Angular准则。通过跟踪应用程序状态的所有更新, 它还有助于调试。
通用商店服务还可以用于管理不使用Firebase功能的应用程序的状态, 以仅管理应用程序的数据或来自其他API的数据。
但是, 在不加选择地应用此功能之前, 需要考虑的一件事是, EmployeesService在构造函数上订阅了Firestore, 并在应用程序处于活动状态时保持监听。如果我们在应用程序的多个页面上使用员工列表, 这可能会很有用, 以避免在页面之间导航时从Firestore获取数据。
但这在其他情况下可能不是最好的选择, 例如, 你只需要提取一次初始值, 然后手动触发从Firebase重新加载数据。最重要的是, 了解你的应用程序要求以选择更好的实施方法总是很重要的。
推荐阅读
- 在使用“Contains”时深入研究实体框架的性能
- 后端(使用Gatsby.js和Node.js进行静态网站更新)
- 数据仓库开发的三项原则
- 算术(使用Orchestrators扩展微服务应用程序)
- 如何使用Firebase身份验证构建基于角色的API
- Windows 10上免费的28款最佳OCR软件下载推荐合集(哪个最好())
- Windows 10如何修复无法安装累积更新KB5008212(解决办法)
- Windows 10如何修复卡在诊断PC的问题(解决办法介绍)
- 前16名最佳预算平板手机购买推荐合集(你最喜欢哪一个())