Manajemen Status di Angular Menggunakan Firebase
Diterbitkan: 2022-03-11Manajemen status adalah bagian arsitektur yang sangat penting untuk dipertimbangkan saat mengembangkan aplikasi web.
Dalam tutorial ini, kita akan membahas pendekatan sederhana untuk mengelola status dalam aplikasi Angular yang menggunakan Firebase sebagai bagian belakangnya.
Kami akan membahas beberapa konsep seperti negara bagian, toko, dan layanan. Mudah-mudahan, ini akan membantu Anda lebih memahami istilah-istilah ini dan juga lebih memahami perpustakaan manajemen negara bagian lainnya seperti NgRx dan NgXs.
Kami akan membangun halaman admin karyawan untuk mencakup beberapa skenario manajemen negara bagian yang berbeda dan pendekatan yang dapat menanganinya.
Komponen, Layanan, Firestore, dan Manajemen Status di Angular
Pada aplikasi Angular yang khas, kami memiliki komponen dan layanan. Biasanya, komponen akan berfungsi sebagai template tampilan. Layanan akan berisi logika bisnis dan/atau berkomunikasi dengan API eksternal atau layanan lain untuk menyelesaikan tindakan atau mengambil data.
Komponen biasanya akan menampilkan data dan memungkinkan pengguna berinteraksi dengan aplikasi untuk menjalankan tindakan. Saat melakukan ini, data dapat berubah dan aplikasi mencerminkan perubahan tersebut dengan memperbarui tampilan.
Mesin pendeteksi perubahan Angular menangani pemeriksaan ketika nilai dalam komponen yang terikat ke tampilan telah berubah dan memperbarui tampilan yang sesuai.
Seiring pertumbuhan aplikasi, kami akan mulai memiliki lebih banyak komponen dan layanan. Seringkali memahami bagaimana data berubah dan melacak di mana itu terjadi bisa jadi rumit.
Sudut dan Firebase
Saat kami menggunakan Firebase sebagai back-end kami, kami disediakan dengan API yang sangat rapi yang berisi sebagian besar operasi dan fungsionalitas yang kami butuhkan untuk membangun aplikasi waktu nyata.
@angular/fire
adalah perpustakaan resmi Angular Firebase. Ini adalah lapisan di atas library Firebase JavaScript SDK yang menyederhanakan penggunaan Firebase SDK di aplikasi Angular. Ini memberikan kecocokan yang baik dengan praktik baik Angular seperti menggunakan Observables untuk mendapatkan dan menampilkan data dari Firebase ke komponen kami.
Toko dan Negara
Kita dapat menganggap "status" sebagai nilai yang ditampilkan pada titik waktu tertentu di aplikasi. Toko hanyalah pemegang status aplikasi itu.
Keadaan dapat dimodelkan sebagai objek polos tunggal atau serangkaian dari mereka, yang mencerminkan nilai-nilai aplikasi.
Aplikasi Sampel Angular/Firebase
Mari kita membangunnya: Pertama, kita akan membuat perancah aplikasi dasar menggunakan Angular CLI, dan menghubungkannya dengan proyek 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
Dan, di styles.scss
:
// ... @import "~bootstrap/scss/bootstrap";
Selanjutnya, kita akan menginstal @angular/fire
:
npm install firebase @angular/fire
Sekarang, kita akan membuat Proyek Firebase di konsol Firebase.
Kemudian kita siap untuk membuat database Firestore.
Untuk tutorial ini, saya akan mulai dalam mode uji. Jika Anda berencana untuk merilis ke produksi, Anda harus menegakkan aturan untuk melarang akses yang tidak pantas.
Buka Ikhtisar Proyek → Pengaturan Proyek, dan salin konfigurasi web Firebase ke environments/environment.ts
lokal Anda.
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>" } };
Pada titik ini, kami memiliki perancah dasar untuk aplikasi kami. Jika kita ng serve
, kita akan mendapatkan:
Kelas Firestore dan Store Base
Kami akan membuat dua kelas abstrak generik, yang kemudian akan kami ketik dan kembangkan untuk membangun layanan kami.
Generik memungkinkan Anda menulis perilaku tanpa tipe terikat. Ini menambahkan penggunaan kembali dan fleksibilitas pada kode Anda.
Layanan Firestore Umum
Untuk memanfaatkan generik TypeScript, yang akan kita lakukan adalah membuat pembungkus generik dasar untuk layanan @angular/fire
firestore
.
Mari kita buat app/core/services/firestore.service.ts
.
Berikut kodenya:
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
ini akan berfungsi sebagai pembungkus generik untuk layanan Firestore kami.
Ini harus menjadi satu-satunya tempat di mana kita harus menyuntikkan AngularFirestore
. Ini akan meminimalkan dampak ketika perpustakaan @angular/fire
diperbarui. Juga, jika suatu saat kita ingin mengubah perpustakaan, kita hanya perlu memperbarui kelas ini.
Saya menambahkan doc$
, collection$
, create
, dan delete
. Mereka membungkus metode @angular/fire
dan menyediakan logging ketika Firebase mengalirkan data—ini akan menjadi sangat berguna untuk debugging—dan setelah sebuah objek dibuat atau dihapus.
Layanan Toko Umum
Layanan toko generik kami akan dibangun menggunakan BehaviorSubject
RxJS. BehaviorSubject
memungkinkan pelanggan mendapatkan nilai yang dipancarkan terakhir segera setelah mereka berlangganan. Dalam kasus kami, ini membantu karena kami akan dapat memulai toko dengan nilai awal untuk semua komponen kami ketika mereka berlangganan ke toko.
Toko akan memiliki dua metode, patch
dan set
. (Kami akan membuat metode get
nanti.)
Mari kita buat 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) } }
Sebagai kelas generik, kami akan menunda pengetikan hingga diperpanjang dengan benar.
Konstruktor akan menerima nilai awal tipe Partial<T>
. Ini akan memungkinkan kita untuk hanya menerapkan nilai ke beberapa properti negara bagian. Konstruktor juga akan berlangganan emisi BehaviorSubject
internal dan terus memperbarui status internal setelah setiap perubahan.
patch()
akan menerima newValue
dari tipe Partial<T>
dan akan menggabungkannya dengan nilai this.state
store saat ini. Terakhir, kita next()
newState
dan memancarkan status baru ke semua pelanggan toko.
set()
bekerja sangat mirip, hanya saja alih-alih menambal nilai status, itu akan mengaturnya ke newValue
yang diterimanya.
Kami akan mencatat nilai status sebelumnya dan berikutnya saat terjadi perubahan, yang akan membantu kami men-debug dan melacak perubahan status dengan mudah.
Menyatukan Semuanya
Oke, mari kita lihat semua ini beraksi. Yang akan kita lakukan adalah membuat halaman karyawan, yang akan berisi daftar karyawan, ditambah formulir untuk menambah karyawan baru.
Mari perbarui app.component.html
untuk menambahkan bilah navigasi sederhana:
<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>
Selanjutnya, kita akan membuat modul Core:
ng gm Core
Di core/core.module.ts
, kami akan menambahkan modul yang diperlukan untuk aplikasi kami:
// ... 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 { }
Sekarang, mari kita buat halaman karyawan, dimulai dengan modul Karyawan:
ng gm Employees --routing
Di employees-routing.module.ts
, mari tambahkan rute employees
:
// ... import { EmployeesPageComponent } from './components/employees-page/employees-page.component'; // ... const routes: Routes = [ { path: 'employees', component: EmployeesPageComponent } ]; // ...
Dan di employees.module.ts
, kita akan mengimpor ReactiveFormsModule
:
// ... import { ReactiveFormsModule } from '@angular/forms'; // ... @NgModule({ // ... imports: [ // ... ReactiveFormsModule ] }) export class EmployeesModule { }
Sekarang, mari tambahkan dua modul ini di file app.module.ts
:
// ... import { EmployeesModule } from './employees/employees.module'; import { CoreModule } from './core/core.module'; imports: [ // ... CoreModule, EmployeesModule ],
Terakhir, mari kita buat komponen aktual dari halaman karyawan kita, ditambah model, layanan, toko, dan status yang sesuai.
ng gc employees/components/EmployeesPage ng gc employees/components/EmployeesList ng gc employees/components/EmployeesForm
Untuk model kita, kita membutuhkan file bernama models/employee.ts
:
export interface Employee { id: string; name: string; location: string; hasDriverLicense: boolean; }
Layanan kami akan hidup dalam file bernama employees/services/employee.firestore.ts
. Layanan ini akan memperluas FirestoreService<T>
generik yang dibuat sebelumnya, dan kami hanya akan mengatur basePath
dari koleksi Firestore:
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'; }
Kemudian kita akan membuat file employees/states/employees-page.ts
. Ini akan berfungsi sebagai status halaman karyawan:
import { Employee } from '../models/employee'; export interface EmployeesPage { loading: boolean; employees: Employee[]; formStatus: string; }
Status akan memiliki nilai loading
yang menentukan apakah akan menampilkan pesan pemuatan pada halaman, employees
itu sendiri, dan variabel formStatus
untuk menangani status formulir (mis. Saving
atau Saved
.)
Kami membutuhkan file di employees/services/employees-page.store.ts
. Di sini kita akan memperluas StoreService<T>
yang dibuat sebelumnya. Kami akan menetapkan nama toko, yang akan digunakan untuk mengidentifikasinya saat debugging.
Layanan ini akan menginisialisasi dan menahan status halaman karyawan. Perhatikan bahwa konstruktor memanggil super()
dengan status awal halaman. Dalam hal ini, kami akan menginisialisasi status dengan loading=true
dan array kosong dari karyawan.
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: [], }) } }
Sekarang mari kita buat EmployeesService
untuk mengintegrasikan EmployeeFirestore
dan EmployeesPageStore
:
ng gs employees/services/Employees
Perhatikan bahwa kami menyuntikkan EmployeeFirestore
dan EmployeesPageStore
dalam layanan ini. Ini berarti bahwa EmployeesService
akan memuat dan mengoordinasikan panggilan ke Firestore dan toko untuk memperbarui status. Ini akan membantu kami membuat satu API untuk komponen yang akan dipanggil.
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") }) } }
Mari kita lihat bagaimana layanan ini akan bekerja.

Di konstruktor, kami akan berlangganan koleksi karyawan Firestore. Segera setelah Firestore mengeluarkan data dari koleksi, kami akan memperbarui toko, menyetel loading=false
dan employees
dengan koleksi yang dikembalikan Firestore. Karena kami telah menyuntikkan EmployeeFirestore
, objek yang dikembalikan dari Firestore diketik ke Employee
, yang memungkinkan lebih banyak fitur IntelliSense.
Langganan ini akan hidup saat aplikasi aktif, mendengarkan semua perubahan dan memperbarui toko setiap kali Firestore mengalirkan data.
this.firestore.collection$().pipe( tap(employees => { this.store.patch({ loading: false, employees, }, `employees collection subscription`) }) ).subscribe()
Fungsi employees$()
dan loading$()
akan memilih bagian dari status yang ingin kita gunakan nanti pada komponen. employees$()
akan mengembalikan array kosong saat status sedang dimuat. Ini akan memungkinkan kita untuk menampilkan pesan yang tepat pada tampilan.
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)) }
Oke, jadi sekarang kita sudah menyiapkan semua layanan, dan kita bisa membangun komponen tampilan kita. Tetapi sebelum kita melakukannya, penyegaran cepat mungkin berguna…
RxJs Observables dan Pipa async
Observables memungkinkan pelanggan untuk menerima emisi data sebagai aliran. Ini, dalam kombinasi dengan pipa async
, bisa sangat kuat.
Pipa async
menangani berlangganan Observable dan memperbarui tampilan saat data baru dipancarkan. Lebih penting lagi, itu secara otomatis berhenti berlangganan ketika komponen dihancurkan, melindungi kita dari kebocoran memori.
Anda dapat membaca lebih lanjut tentang Observables dan perpustakaan RxJs secara umum di dokumen resmi.
Membuat Komponen Tampilan
Di employees/components/employees-page/employees-page.component.html
, kami akan memasukkan kode ini:
<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>
Demikian juga, employees/components/employees-list/employees-list.component.html
akan memiliki ini, menggunakan teknik pipa async
yang disebutkan di atas:
<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>
Tetapi dalam hal ini kita juga memerlukan beberapa kode TypeScript untuk komponen tersebut. File employees/components/employees-list/employees-list.component.ts
akan membutuhkan ini:
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); } }
Jadi, pergi ke browser, yang akan kita miliki sekarang adalah:
Dan konsol akan memiliki output berikut:
Melihat ini, kami dapat mengatakan bahwa Firestore mengalirkan koleksi employees
dengan nilai kosong, dan toko employees-page
telah ditambal, menyetel loading
dari true
ke false
.
Oke, mari kita buat formulir untuk menambahkan karyawan baru ke Firestore:
Formulir Karyawan
Di employees/components/employees-form/employees-form.component.html
kita akan menambahkan kode ini:
<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>
Kode TypeScript yang sesuai akan tinggal di 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() } }
Formulir akan memanggil metode create()
dari EmployeesService
. Sekarang halamannya terlihat seperti ini:
Mari kita lihat apa yang terjadi ketika kita menambahkan karyawan baru.
Menambahkan Karyawan Baru
Setelah menambahkan karyawan baru, kita akan melihat output get berikut ke konsol:
Ini semua adalah peristiwa yang dipicu saat menambahkan karyawan baru. Mari kita lihat lebih dekat.
Saat kita memanggil create()
kita akan mengeksekusi kode berikut, menyetel loading=true
, formStatus='Saving...'
dan array employees
menjadi kosong ( (1)
pada gambar di atas).
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") })
Selanjutnya, kami memanggil layanan Firestore dasar untuk membuat karyawan, yang mencatat (4)
. Pada panggilan balik janji, kami menyetel formStatus='Saved!'
dan log (5)
. Akhirnya, kami menetapkan batas waktu untuk mengatur formStatus
kembali ke kosong, logging (6)
.
Peristiwa log (2)
dan (3)
adalah peristiwa yang dipicu oleh langganan Firestore ke koleksi karyawan. Ketika EmployeesService
dipakai, kami berlangganan koleksi dan menerima koleksi pada setiap perubahan yang terjadi.
Ini menetapkan status baru ke toko dengan loading=false
dengan mengatur array employees
ke karyawan yang berasal dari Firestore.
Jika kami memperluas grup log, kami akan melihat data terperinci dari setiap peristiwa dan pembaruan toko, dengan nilai sebelumnya dan berikutnya, yang berguna untuk debugging.
Beginilah tampilan halaman setelah menambahkan karyawan baru:
Menambahkan Komponen Ringkasan
Katakanlah kita sekarang ingin menampilkan beberapa data ringkasan di halaman kita. Katakanlah kita ingin jumlah total karyawan, berapa banyak pengemudi, dan berapa banyak yang berasal dari Rosario.
Kami akan mulai dengan menambahkan properti status baru ke model status halaman di employees/states/employees-page.ts
:
// ... export interface EmployeesPage { loading: boolean; employees: Employee[]; formStatus: string; totalEmployees: number; totalDrivers: number; totalRosarioEmployees: number; }
Dan kami akan menginisialisasinya di toko di employees/services/emplyees-page.store.ts
:
// ... constructor() { super({ loading: true, employees: [], totalDrivers: 0, totalEmployees: 0, totalRosarioEmployees: 0 }) } // ...
Selanjutnya, kita akan menghitung nilai untuk properti baru dan menambahkan pemilihnya masing-masing di 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)) } // ...
Sekarang, mari kita buat komponen ringkasan:
ng gc employees/components/EmployeesSummary
Kami akan menempatkan ini di 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>
Dan di 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$; } }
Kami kemudian akan menambahkan komponen ke employees/employees-page/employees-page.component.html
:
// ... <div class="col-12 mb-3"> <h4> Employees </h4> <app-employees-summary></app-employees-summary> </div> // ...
Hasilnya adalah sebagai berikut:
Di konsol kami memiliki:
Layanan karyawan menghitung total totalEmployees
, totalDrivers
, dan totalRosarioEmployees
pada setiap emisi dan memperbarui status.
Kode lengkap dari tutorial ini tersedia di GitHub, dan ada juga demo langsung.
Mengelola Status Aplikasi Angular Menggunakan Observables… Periksa!
Dalam tutorial ini, kami membahas pendekatan sederhana untuk mengelola status di aplikasi Angular menggunakan back end Firebase.
Pendekatan ini sangat cocok dengan pedoman Angular menggunakan Observables. Ini juga memfasilitasi debugging dengan menyediakan pelacakan untuk semua pembaruan ke status aplikasi.
Layanan toko generik juga dapat digunakan untuk mengelola status aplikasi yang tidak menggunakan fitur Firebase, baik untuk mengelola hanya data aplikasi atau data yang berasal dari API lain.
Tetapi sebelum Anda menerapkan ini tanpa pandang bulu, satu hal yang perlu dipertimbangkan adalah bahwa EmployeesService
berlangganan Firestore di konstruktor dan terus mendengarkan saat aplikasi aktif. Ini mungkin berguna jika kita menggunakan daftar karyawan di beberapa halaman di aplikasi, untuk menghindari mendapatkan data dari Firestore saat menavigasi antar halaman.
Tetapi ini mungkin bukan opsi terbaik dalam skenario lain seperti jika Anda hanya perlu menarik nilai awal sekali dan kemudian secara manual memicu pemuatan ulang data dari Firebase. Intinya adalah, selalu penting untuk memahami persyaratan aplikasi Anda untuk memilih metode implementasi yang lebih baik.