Firebase Kullanarak Angular'da Durum Yönetimi

Yayınlanan: 2022-03-11

Durum 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, diğer hizmetlere veya HTTP API'lerine bağlanan hizmetlere bağlanı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.

Bileşenler, Observables kullanarak @angular/fire aracılığıyla Firebase JavaScript API'sine abone olur.

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.

Ad, şehir ve ülke için bazı basit anahtar/değer çiftleri içeren örnek bir nesneye sahip mağaza tutma durumu.

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.

Firebase konsolunun "Proje ekle" iletişim kutusu.

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.

"Kilitli modda başlat" yerine "Test modunda başlat" seçili olarak "Cloud Firestore için güvenlik kuralları" iletişim kutusu.

Projeye Genel Bakış → Proje Ayarları'na gidin ve Firebase web yapılandırmasını yerel environments/environment.ts kopyalayın.

Yeni Firebase projesi için boş uygulamalar listesi.

 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:

Açısal iskele, "Çalışanlara-yöneticiye hoş geldiniz!"

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:

Boş bir çalışan listesi ve "çalışan formu çalışır!" mesajı.

Ve konsol aşağıdaki çıktıya sahip olacaktır:

Öncesi ve sonrası değerleriyle değişiklikleri gösteren yama olayları.

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:

Daha önce olduğu gibi aynı boş çalışan listesi, bu sefer yeni bir çalışan eklemek için bir form ile.

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:

Firestore olaylarıyla karıştırılmış, birden altıya kadar numaralandırılmış yama olayları (yerel oluşturma, Firestore koleksiyonu akışı, yerel koleksiyon aboneliği, Firestore oluşturma, yerel oluşturma başarısı ve yerel oluşturma zaman aşımı formu durum sıfırlaması.)

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.

Tüm durum yönetimi ayrıntılarıyla birlikte önceki günlük çıktısı genişletildi.

Yeni bir çalışan ekledikten sonra sayfa şu şekilde görünür:

İçinde bir çalışan kartı bulunan çalışan listesi ve eklendikten sonra yine de doldurulan form.

Ö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:

Çalışanlar sayfası, şimdi listenin üzerinde bir özet ile toplam çalışan, sürücü ve Rosario'dan gelenlerin sayısını gösteriyor.

Konsolda elimizde:

Özet değerlerini değiştiren bir yama olayını gösteren konsol çıktısı.

Ç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.