个性化阅读
专注于IT技术分析

使用Firebase在Angular中进行状态管理

点击下载

本文概述

状态管理是开发Web应用程序时要考虑的非常重要的体系结构。

在本教程中, 我们将介绍一种简单的方法来管理以Firebase为后端的Angular应用程序中的状态。

我们将介绍一些概念, 例如州, 商店和服务。希望这将帮助你更好地理解这些术语, 并更好地理解其他状态管理库, 例如NgRx和NgXs。

我们将建立一个员工管理页面, 以涵盖一些不同的状态管理方案以及可以解决这些方案的方法。

Angular中的组件, 服务, Firestore和状态管理

在典型的Angular应用程序中, 我们具有组件和服务。通常, 组件将用作视图模板。服务将包含业务逻辑和/或与外部API或其他服务进行通信以完成操作或检索数据。

组件连接到服务,而服务又连接到其他服务或HTTP API。

组件通常会显示数据, 并允许用户与应用交互以执行操作。在执行此操作时, 数据可能会更改, 并且应用程序会通过更新视图来反映这些更改。

Angular的更改检测引擎负责检查绑定到视图的组件中的值何时发生更改, 并相应地更新视图。

随着应用的发展, 我们将开始拥有越来越多的组件和服务。通常, 了解数据如何变化并跟踪发生的位置可能很棘手。

Angular和Firebase

当我们使用Firebase作为后端时, 我们将获得一个真正简洁的API, 其中包含构建实时应用程序所需的大多数操作和功能。

@ angular / fire是官方的Angular Firebase库。该层位于Firebase JavaScript SDK库的顶层, 可简化Angular应用中Firebase SDK的使用。它非常适合Angular的良好做法, 例如使用Observables从Firebase获取和显示数据到我们的组件。

组件使用Observables通过@ angular / fire订阅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 Web配置复制到本地环境/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>"
    }
};

至此, 我们已经为我们的应用准备了基本的支架。如果我们提供服务, 我们将获得:

Angular支架,说"欢迎员工-管理员!"

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 { }

现在, 从”雇员”模块开始创建”雇员”页面:

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()方法。现在页面看起来像这样:

与以前相同的空员工列表,这次有一个添加新员工的表格。

让我们看看添加新员工时会发生什么。

添加新员工

添加新员工后, 我们将看到以下内容输出到控制台:

修补程序事件与Firestore事件混合在一起,编号从1到6(本地创建,Firestore集合流,本地集合订阅,Firestore创建,本地创建成功以及本地创建超时表单状态重置)。

这些都是添加新员工时触发的所有事件。让我们仔细看看。

当我们调用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>
// ...

结果如下:

员工页面,现在列表上方有一个摘要,显示了员工总数,司机人数以及来自Rosario的人数。

在控制台中, 我们有:

控制台输出显示补丁事件,该事件更改了摘要值。

员工服务计算每个排放的total totalEmployees, totalDrivers和totalRosarioEmployees并更新状态。

本教程的完整代码可在GitHub上找到, 并且还有一个实时演示。

使用Observables管理Angular应用程序状态…检查!

在本教程中, 我们介绍了一种使用Firebase后端在Angular应用程序中管理状态的简单方法。

这种方法非常适合使用Observables的Angular准则。通过跟踪应用程序状态的所有更新, 它还有助于调试。

通用商店服务还可以用于管理不使用Firebase功能的应用程序的状态, 以仅管理应用程序的数据或来自其他API的数据。

但是, 在不加选择地应用此功能之前, 需要考虑的一件事是, EmployeesService在构造函数上订阅了Firestore, 并在应用程序处于活动状态时保持监听。如果我们在应用程序的多个页面上使用员工列表, 这可能会很有用, 以避免在页面之间导航时从Firestore获取数据。

但这在其他情况下可能不是最好的选择, 例如, 你只需要提取一次初始值, 然后手动触发从Firebase重新加载数据。最重要的是, 了解你的应用程序要求以选择更好的实施方法总是很重要的。

赞(1)
未经允许不得转载:srcmini » 使用Firebase在Angular中进行状态管理

评论 抢沙发

评论前必须登录!