Firebase Kullanarak Angular'da Durum Yönetimi
Yayınlanan: 2022-03-11Durum yönetimi, bir web uygulaması geliştirirken dikkate alınması gereken çok önemli bir mimaridir.
Bu öğreticide, arka ucu olarak Firebase kullanan bir Angular uygulamasında durumu yönetmek için basit bir yaklaşımı gözden geçireceğiz.
Devlet, mağazalar ve hizmetler gibi bazı kavramların üzerinden geçeceğiz. Umarım bu, bu terimleri daha iyi anlamanıza ve ayrıca NgRx ve NgX'ler gibi diğer durum yönetimi kitaplıklarını daha iyi anlamanıza yardımcı olur.
Bazı farklı durum yönetimi senaryolarını ve bunların üstesinden gelebilecek yaklaşımları kapsayacak şekilde bir çalışan yönetici sayfası oluşturacağız.
Angular'da Bileşenler, Hizmetler, Firestore ve Durum Yönetimi
Tipik bir Angular uygulamasında bileşenlerimiz ve hizmetlerimiz var. Genellikle, bileşenler görünüm şablonu görevi görür. Hizmetler, eylemleri tamamlamak veya verileri almak için iş mantığını içerecek ve/veya harici API'ler veya diğer hizmetlerle iletişim kuracaktır.
Bileşenler genellikle verileri görüntüler ve kullanıcıların eylemleri gerçekleştirmek için uygulamayla etkileşime girmesine izin verir. Bunu yaparken veriler değişebilir ve uygulama, görünümü güncelleyerek bu değişiklikleri yansıtır.
Angular'ın değişiklik algılama motoru, görünüme bağlı bir bileşendeki bir değerin ne zaman değiştiğini kontrol eder ve görünümü buna göre günceller.
Uygulama büyüdükçe, daha fazla bileşen ve hizmete sahip olmaya başlayacağız. Genellikle verilerin nasıl değiştiğini anlamak ve bunun nerede olduğunu takip etmek zor olabilir.
Açısal ve Firebase
Firebase'i arka uç olarak kullandığımızda, gerçek zamanlı bir uygulama oluşturmak için ihtiyaç duyduğumuz işlemlerin ve işlevlerin çoğunu içeren gerçekten temiz bir API sağlanır.
@angular/fire
resmi Angular Firebase kütüphanesidir. Bir Angular uygulamasında Firebase SDK'nın kullanımını basitleştiren, Firebase JavaScript SDK kitaplığının üstündeki bir katmandır. Firebase'den bileşenlerimize veri almak ve görüntülemek için Observables kullanmak gibi Angular iyi uygulamalarıyla güzel bir uyum sağlar.
Mağazalar ve Eyalet
“Durum”u, uygulamada herhangi bir zamanda görüntülenen değerler olarak düşünebiliriz. Mağaza, yalnızca bu uygulama durumunun sahibidir.
Durum, uygulamanın değerlerini yansıtan tek bir düz nesne veya bir dizi olarak modellenebilir.
Angular/Firebase Örnek Uygulaması
Haydi inşa edelim: İlk olarak, Angular CLI kullanarak temel bir uygulama iskelesi oluşturacağız ve onu bir Firebase projesine bağlayacağız.
$ 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
Ve styles.scss
üzerinde:
// ... @import "~bootstrap/scss/bootstrap";
Ardından, @angular/fire
yükleyeceğiz:
npm install firebase @angular/fire
Şimdi, Firebase konsolunda bir Firebase Projesi oluşturacağız.
Ardından bir Firestore veritabanı oluşturmaya hazırız.
Bu eğitim için test modunda başlayacağım. Üretime sunmayı planlıyorsanız, uygunsuz erişimi yasaklayan kuralları uygulamalısınız.
Projeye Genel Bakış → Proje Ayarları'na gidin ve Firebase web yapılandırmasını yerel environments/environment.ts
kopyalayın.
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>" } };
Bu noktada, uygulamamız için temel yapı iskelesine sahibiz. Eğer ng serve
edersek, şunları elde ederiz:
Firestore ve Mağaza Temel Sınıfları
Daha sonra yazacağımız ve hizmetlerimizi oluşturmak için genişleteceğimiz iki genel soyut sınıf oluşturacağız.
Jenerikler, bağlı bir tür olmadan davranış yazmanıza izin verir. Bu, kodunuza yeniden kullanılabilirlik ve esneklik katar.
Genel Firestore Hizmeti
TypeScript jeneriklerinden yararlanmak için, @angular/fire
firestore
hizmeti için temel bir genel sarmalayıcı oluşturacağız.
app/core/services/firestore.service.ts
oluşturalım.
İşte kod:
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}`); } }
Bu abstract class
, Firestore hizmetlerimiz için genel bir sarmalayıcı olarak çalışacaktır.
AngularFirestore
enjekte etmemiz gereken tek yer burası olmalı. Bu, @angular/fire
kitaplığı güncellendiğinde etkiyi en aza indirecektir. Ayrıca, eğer bir noktada kütüphaneyi değiştirmek istersek, sadece bu sınıfı güncellememiz gerekecek.
doc$
, collection$
, create
ve delete
öğelerini ekledim. @angular/fire
yöntemlerini sararlar ve Firebase veri akışı yaptığında (bu, hata ayıklama için çok kullanışlı olacaktır) ve bir nesne oluşturulduktan veya silindikten sonra günlük kaydı sağlar.
Genel Mağaza Hizmeti
Genel mağaza hizmetimiz, RxJS' BehaviorSubject
kullanılarak oluşturulacaktır. BehaviorSubject
, abonelerin abone oldukları anda son yayınlanan değeri almalarını sağlar. Bizim durumumuzda bu yararlıdır çünkü mağazaya abone olduklarında tüm bileşenlerimiz için bir başlangıç değeri ile mağazaya başlayabileceğiz.
Mağazanın patch
ve set
olmak üzere iki yöntemi olacaktır. (Daha sonra get
yöntemlerini oluşturacağız.)
app/core/services/store.service.ts
oluşturalım:
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) } }
Genel bir sınıf olarak, uygun şekilde genişletilene kadar yazmayı erteleyeceğiz.
Yapıcı, Partial<T>
türünün başlangıç değerini alacaktır. Bu, değerleri yalnızca devletin bazı özelliklerine uygulamamıza izin verecektir. Yapıcı ayrıca dahili BehaviorSubject
emisyonlarına abone olacak ve her değişiklikten sonra dahili durumu güncel tutacaktır.
patch()
, Partial<T>
türünden newValue
değerini alacak ve onu mağazanın geçerli this.state
değeriyle birleştirecektir. Son olarak, next()
newState
ve yeni durumu tüm mağaza abonelerine yayarız.
set()
çok benzer şekilde çalışır, yalnızca durum değerini yamalamak yerine aldığı newValue
.
Değişiklikler meydana geldikçe durumun önceki ve sonraki değerlerini günlüğe kaydedeceğiz, bu da hata ayıklamamıza ve durum değişikliklerini kolayca izlememize yardımcı olacaktır.
Hepsini bir araya koy
Tamam, tüm bunları çalışırken görelim. Yapacağımız şey, çalışanların listesini ve yeni çalışanlar eklemek için bir form içeren bir çalışanlar sayfası oluşturmak.
Basit bir gezinme çubuğu eklemek için app.component.html
güncelleyelim:
<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>
Ardından, bir Çekirdek modül oluşturacağız:
ng gm Core
core/core.module.ts
, uygulamamız için gerekli modülleri ekleyeceğiz:
// ... 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 { }
Şimdi Çalışanlar modülünden başlayarak çalışanlar sayfasını oluşturalım:
ng gm Employees --routing
employees-routing.module.ts
içinde, employees
rotasını ekleyelim:
// ... import { EmployeesPageComponent } from './components/employees-page/employees-page.component'; // ... const routes: Routes = [ { path: 'employees', component: EmployeesPageComponent } ]; // ...
Ve employees.module.ts
ReactiveFormsModule
içe aktaracağız:
// ... import { ReactiveFormsModule } from '@angular/forms'; // ... @NgModule({ // ... imports: [ // ... ReactiveFormsModule ] }) export class EmployeesModule { }
Şimdi bu iki modülü app.module.ts
dosyasına ekleyelim:
// ... import { EmployeesModule } from './employees/employees.module'; import { CoreModule } from './core/core.module'; imports: [ // ... CoreModule, EmployeesModule ],
Son olarak, çalışanlar sayfamızın gerçek bileşenlerini artı ilgili model, hizmet, mağaza ve durumu oluşturalım.
ng gc employees/components/EmployeesPage ng gc employees/components/EmployeesList ng gc employees/components/EmployeesForm
Modelimiz için models/employee.ts
adlı bir dosyaya ihtiyacımız olacak:
export interface Employee { id: string; name: string; location: string; hasDriverLicense: boolean; }
Hizmetimiz, employees/services/employee.firestore.ts
adlı bir dosyada yaşayacak. Bu hizmet, daha önce oluşturulan genel FirestoreService<T>
genişletecek ve biz yalnızca Firestore koleksiyonunun basePath
ayarlayacağız:
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'; }
Ardından employees/states/employees-page.ts
dosyasını oluşturacağız. Bu, çalışanların durumu sayfası olarak hizmet edecektir:
import { Employee } from '../models/employee'; export interface EmployeesPage { loading: boolean; employees: Employee[]; formStatus: string; }
Durum, sayfada bir yükleme mesajının, employees
kendilerinin ve formun durumunu işlemek için bir formStatus
değişkeninin (örneğin, Saving
veya Saved
) görüntülenip görüntülenmeyeceğini belirleyen bir loading
değerine sahip olacaktır.
employees/services/employees-page.store.ts
adresinde bir dosyaya ihtiyacımız olacak. Burada daha önce oluşturulan StoreService<T>
genişleteceğiz. Hata ayıklarken onu tanımlamak için kullanılacak mağaza adını ayarlayacağız.
Bu hizmet, çalışanlar sayfasının durumunu başlatacak ve tutacaktır. Yapıcının sayfanın ilk durumuyla super()
öğesini çağırdığını unutmayın. Bu durumda, durumu loading=true
ve boş bir çalışan dizisi ile başlatacağız.
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: [], }) } }
Şimdi, EmployeeFirestore
ve EmployeesPageStore
entegre etmek için EmployeesService
oluşturalım:
ng gs employees/services/Employees
Bu hizmette EmployeeFirestore
ve EmployeesPageStore
enjekte ettiğimizi unutmayın. Bu, EmployeesService
Hizmetinin durumu güncellemek için Firestore ve mağazaya yapılan çağrıları içereceği ve koordine edeceği anlamına gelir. Bu, bileşenlerin çağrılması için tek bir API oluşturmamıza yardımcı olacaktır.
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") }) } }
Servisin nasıl çalışacağına bir göz atalım.

Yapıcıda, Firestore çalışanları koleksiyonuna abone olacağız. Firestore, koleksiyondan veri yaydığı anda, loading=false
ve Firestore'un iade edilen koleksiyonuna sahip employees
ayarlayarak mağazayı güncelleyeceğiz. EmployeeFirestore
öğesini enjekte ettiğimiz için, Firestore'dan döndürülen nesneler, daha fazla IntelliSense özelliği sağlayan Employee
öğesine yazılır.
Bu abonelik, uygulama etkinken, tüm değişiklikleri dinlerken ve Firestore her veri akışında mağazayı güncellerken canlı olacaktır.
this.firestore.collection$().pipe( tap(employees => { this.store.patch({ loading: false, employees, }, `employees collection subscription`) }) ).subscribe()
employees$()
ve loading$()
işlevleri, bileşen üzerinde daha sonra kullanmak istediğimiz durum parçasını seçecektir. employees$()
, durum yüklenirken boş bir dizi döndürür. Bu, görünümde uygun mesajlaşmayı göstermemize izin verecektir.
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)) }
Tamam, şimdi tüm servislerimiz hazır ve görünüm bileşenlerimizi oluşturabiliriz. Ama bunu yapmadan önce, hızlı bir tazeleme işimize yarayabilir…
RxJs Gözlenebilirleri ve zaman async
Boru
Gözlenebilirler, abonelerin veri emisyonlarını bir akış olarak almasına izin verir. Bu, zaman async
boru ile birlikte çok güçlü olabilir.
Zaman async
boru, bir Gözlenebilir'e abone olma ve yeni veriler yayıldığında görünümü güncelleme ile ilgilenir. Daha da önemlisi, bileşen yok edildiğinde otomatik olarak abonelikten çıkar ve bizi bellek sızıntılarından korur.
Resmi belgelerde genel olarak Gözlenebilirler ve RxJs kitaplığı hakkında daha fazla bilgi edinebilirsiniz.
Görünüm Bileşenlerini Oluşturma
employees/components/employees-page/employees-page.component.html
içine şu kodu koyacağız:
<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>
Benzer şekilde, yukarıda belirtilen zaman async
boru tekniğini kullanarak employees/components/employees-list/employees-list.component.html
buna sahip olacaktır:
<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>
Ancak bu durumda, bileşen için de bir TypeScript koduna ihtiyacımız olacak. employees/components/employees-list/employees-list.component.ts
buna ihtiyacı olacaktır:
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); } }
Yani, tarayıcıya gittiğimizde, şimdi sahip olacağımız şey:
Ve konsol aşağıdaki çıktıya sahip olacaktır:
Buna bakarak, Firestore'un employees
koleksiyonunu boş değerlerle yayınladığını ve employees-page
mağazasının yamalandığını ve loading
true
false
ayarlandığını söyleyebiliriz.
Tamam, Firestore'a yeni çalışanlar eklemek için formu oluşturalım:
Çalışan Formu
employees/components/employees-form/employees-form.component.html
şu kodu ekleyeceğiz:
<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>
İlgili TypeScript kodu, 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() } }
Form, EmployeesService
öğesinin create()
yöntemini çağıracaktır. Şu anda sayfa şöyle görünüyor:
Yeni bir çalışan eklediğimizde ne olduğuna bir göz atalım.
Yeni Çalışan Ekleme
Yeni bir çalışan ekledikten sonra, konsola aşağıdaki get çıktısını göreceğiz:
Bunlar, yeni bir çalışan eklerken tetiklenen tüm olaylardır. Hadi daha yakından bakalım.
create()
'i çağırdığımızda, loading=true
, formStatus='Saving...'
ayarını yaparak aşağıdaki kodu çalıştıracağız ve employees
dizisini boş olarak ayarlayacağız ( (1)
yukarıdaki resimde).
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") })
Ardından, (4)
günlüğe kaydeden çalışanı oluşturmak için temel Firestore hizmetini çağırıyoruz. Söz verilen geri aramada, formStatus='Saved!'
ayarladık. ve günlüğe kaydet (5)
. Son olarak, formStatus
tekrar boş olarak ayarlamak için bir zaman aşımı belirledik, günlüğe kaydetme (6)
.
Günlük olayları (2)
ve (3)
, çalışanlar koleksiyonuna yönelik Firestore aboneliği tarafından tetiklenen olaylardır. EmployeesService
Hizmeti başlatıldığında, koleksiyona abone olur ve gerçekleşen her değişiklikte koleksiyonu alırız.
Bu, employees
dizisini Firestore'dan gelen çalışanlara ayarlayarak loading=false
ile mağazaya yeni bir durum ayarlar.
Günlük gruplarını genişletirsek, hata ayıklama için yararlı olan önceki ve sonraki değerlerle mağazanın her olayının ve güncellemesinin ayrıntılı verilerini görürüz.
Yeni bir çalışan ekledikten sonra sayfa şu şekilde görünür:
Özet Bileşeni Ekleme
Diyelim ki şimdi sayfamızda bazı özet verileri görüntülemek istiyoruz. Diyelim ki toplam çalışan sayısını, kaçının şoför olduğunu ve kaçının Rosario'dan olduğunu istiyoruz.
employees/states/employees-page.ts
sayfa durumu modeline yeni durum özelliklerini ekleyerek başlayacağız:
// ... export interface EmployeesPage { loading: boolean; employees: Employee[]; formStatus: string; totalEmployees: number; totalDrivers: number; totalRosarioEmployees: number; }
Ve bunları mağazada employees/services/emplyees-page.store.ts
içinde başlatacağız:
// ... constructor() { super({ loading: true, employees: [], totalDrivers: 0, totalEmployees: 0, totalRosarioEmployees: 0 }) } // ...
Ardından, yeni özelliklerin değerlerini hesaplayacağız ve ilgili seçicilerini EmployeesService
Hizmetine ekleyeceğiz:
// ... 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)) } // ...
Şimdi özet bileşenini oluşturalım:
ng gc employees/components/EmployeesSummary
Bunu employees/components/employees-summary/employees-summary.html
içine koyacağız:
<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>
Ve employees/components/employees-summary/employees-summary.ts
içinde:
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$; } }
Ardından, component employees/employees-page/employees-page.component.html
:
// ... <div class="col-12 mb-3"> <h4> Employees </h4> <app-employees-summary></app-employees-summary> </div> // ...
Sonuç aşağıdaki gibidir:
Konsolda elimizde:
Çalışanlar hizmeti, her emisyonda toplam totalEmployees
, totalDrivers
ve totalRosarioEmployees
değerlerini hesaplar ve durumu günceller.
Bu öğreticinin tam kodu GitHub'da mevcuttur ve ayrıca canlı bir demo da vardır.
Açısal Uygulama Durumunun Gözlenebilirleri Kullanarak Yönetilmesi… Kontrol Edin!
Bu eğitici yazıda, bir Firebase arka ucu kullanarak Angular uygulamalarında durumu yönetmek için basit bir yaklaşımı ele aldık.
Bu yaklaşım, Gözlenebilirleri kullanmanın Açısal yönergelerine çok uygundur. Ayrıca, uygulamanın durumuna yönelik tüm güncellemeler için izleme sağlayarak hata ayıklamayı kolaylaştırır.
Genel mağaza hizmeti, yalnızca uygulamanın verilerini veya diğer API'lerden gelen verileri yönetmek için Firebase özelliklerini kullanmayan uygulamaların durumunu yönetmek için de kullanılabilir.
Ancak bunu ayrım gözetmeksizin uygulamaya başlamadan önce, dikkate alınması gereken bir şey, EmployeesService
yapıcıda Firestore'a abone olması ve uygulama etkinken dinlemeye devam etmesidir. Sayfalar arasında gezinirken Firestore'dan veri almayı önlemek için uygulamadaki birden çok sayfada çalışan listesini kullanırsak bu yararlı olabilir.
Ancak bu, yalnızca ilk değerleri bir kez almanız ve ardından Firebase'den manuel olarak yeniden veri yüklemesini tetiklemeniz gerektiği gibi diğer senaryolarda en iyi seçenek olmayabilir. Sonuç olarak, daha iyi uygulama yöntemlerini seçmek için uygulamanızın gereksinimlerini anlamak her zaman önemlidir.