Η διαχείριση του κράτους είναι ένα πολύ σημαντικό κομμάτι της αρχιτεκτονικής που πρέπει να λάβετε υπόψη κατά την ανάπτυξη μιας εφαρμογής ιστού.
Σε αυτό το σεμινάριο, θα εξετάσουμε μια απλή προσέγγιση για τη διαχείριση της κατάστασης σε ένα Γωνιακή εφαρμογή που χρησιμοποιεί Firebase ως το πίσω άκρο του.
Θα εξετάσουμε μερικές έννοιες, όπως πολιτεία, καταστήματα και υπηρεσίες. Ας ελπίσουμε ότι αυτό θα σας βοηθήσει να κατανοήσετε καλύτερα αυτούς τους όρους και να κατανοήσετε καλύτερα άλλες βιβλιοθήκες κρατικής διαχείρισης όπως NgRx και NgXs.
Θα δημιουργήσουμε μια σελίδα διαχειριστή υπαλλήλων για να καλύψουμε ορισμένα διαφορετικά σενάρια διαχείρισης κατάστασης και τις προσεγγίσεις που μπορούν να τα χειριστούν.
Σε ένα τυπικό Γωνιώδης εφαρμογή έχουμε στοιχεία και υπηρεσίες. Συνήθως, τα στοιχεία θα χρησιμεύσουν ως πρότυπο προβολής. Οι υπηρεσίες θα περιέχουν επιχειρηματική λογική ή / και επικοινωνία με εξωτερικά API ή άλλες υπηρεσίες για την ολοκλήρωση ενεργειών ή την ανάκτηση δεδομένων.
Τα στοιχεία θα εμφανίζουν συνήθως δεδομένα και θα επιτρέπουν στους χρήστες να αλληλεπιδρούν με την εφαρμογή για να εκτελούν ενέργειες. Ενώ το κάνετε αυτό, τα δεδομένα ενδέχεται να αλλάξουν και η εφαρμογή αντικατοπτρίζει αυτές τις αλλαγές ενημερώνοντας την προβολή.
Η μηχανή ανίχνευσης αλλαγών της Angular φροντίζει να ελέγξει πότε έχει αλλάξει μια τιμή σε ένα στοιχείο που συνδέεται με την προβολή και ενημερώνει την προβολή αναλόγως.
Καθώς η εφαρμογή μεγαλώνει, θα αρχίσουμε να έχουμε όλο και περισσότερα στοιχεία και υπηρεσίες. Συχνά η κατανόηση του τρόπου με τον οποίο αλλάζουν τα δεδομένα και η παρακολούθηση του πού συμβαίνει μπορεί να είναι δύσκολη.
Όταν χρησιμοποιούμε Firebase ως το πίσω μέρος μας, μας παρέχεται ένα πραγματικά τακτοποιημένο API που περιέχει τις περισσότερες από τις λειτουργίες και τη λειτουργικότητα που χρειαζόμαστε για τη δημιουργία μιας εφαρμογής σε πραγματικό χρόνο.
@angular/fire
είναι η επίσημη βιβλιοθήκη Angular Firebase. Είναι ένα επίπεδο πάνω από τη βιβλιοθήκη SDK του Firebase JavaScript που απλοποιεί τη χρήση του SDK του Firebase σε μια γωνιακή εφαρμογή. Παρέχει μια καλή εφαρμογή με καλές πρακτικές Angular, όπως η χρήση Παρατηρήσιμων για τη λήψη και την εμφάνιση δεδομένων από το Firebase στα στοιχεία μας.
Μπορούμε να σκεφτούμε το 'state' ως τις τιμές που εμφανίζονται σε οποιαδήποτε δεδομένη χρονική στιγμή στην εφαρμογή. Το κατάστημα είναι απλά ο κάτοχος αυτής της κατάστασης εφαρμογής.
Η κατάσταση μπορεί να μοντελοποιηθεί ως ένα απλό αντικείμενο ή μια σειρά από αυτά, αντικατοπτρίζοντας τις τιμές της εφαρμογής.
Ας το δημιουργήσουμε: Πρώτον, θα δημιουργήσουμε ένα βασικό ικρίωμα εφαρμογών χρησιμοποιώντας το Angular CLI και θα το συνδέσουμε με ένα έργο Firebase.
$ npm install -g @angular/cli $ ng new employees-admin` Would you like to add Angular routing? Yes Which stylesheet format would you like to use? SCSS $ cd employees-admin/ $ npm install bootstrap # We'll add Bootstrap for the UI
Και, στις styles.scss
:
// ... @import '~bootstrap/scss/bootstrap';
Στη συνέχεια, θα εγκαταστήσουμε @angular/fire
:
npm install firebase @angular/fire
Τώρα, θα δημιουργήσουμε ένα έργο Firebase στο την κονσόλα Firebase .
Στη συνέχεια, είμαστε έτοιμοι να δημιουργήσουμε μια βάση δεδομένων Firestore.
Για αυτό το σεμινάριο, θα ξεκινήσω σε δοκιμαστική λειτουργία. Εάν σκοπεύετε να κυκλοφορήσετε στην παραγωγή, θα πρέπει να εφαρμόσετε κανόνες για να απαγορεύσετε την ακατάλληλη πρόσβαση.
Μεταβείτε στην Επισκόπηση έργου → Ρυθμίσεις έργου και αντιγράψτε τη διαμόρφωση ιστού Firebase στην τοπική σας environments/environment.ts
.
export const environment = { production: false, firebase: { apiKey: '', authDomain: '', databaseURL: '', projectId: '', storageBucket: '', messagingSenderId: '' } };
Σε αυτό το σημείο, έχουμε το βασικό ικρίωμα για την εφαρμογή μας. Εάν ng serve
, θα λάβουμε:
τι σημαίνει c corporation
Θα δημιουργήσουμε δύο γενικές αφηρημένες τάξεις, τις οποίες στη συνέχεια θα πληκτρολογήσουμε και θα επεκτείνουμε για να δημιουργήσουμε τις υπηρεσίες μας.
Generics σας επιτρέπουν να γράφετε συμπεριφορά χωρίς δεσμευμένο τύπο. Αυτό προσθέτει επαναχρησιμοποίηση και ευελιξία στον κωδικό σας.
Για να επωφεληθούμε από τα γενικά 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 { protected abstract basePath: string; constructor( @Inject(AngularFirestore) protected firestore: AngularFirestore, ) { } doc$(id: string): Observable { return this.firestore.doc(`${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 { return this.firestore.collection(`${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
θα λειτουργήσει ως γενικό περιτύλιγμα για τις υπηρεσίες μας στο Firestore.
Αυτό πρέπει να είναι το μόνο μέρος όπου πρέπει να κάνουμε ένεση AngularFirestore
. Αυτό θα ελαχιστοποιήσει τον αντίκτυπο όταν το @angular/fire
η βιβλιοθήκη ενημερώνεται. Επίσης, εάν κάποια στιγμή θέλουμε να αλλάξουμε τη βιβλιοθήκη, θα χρειαστεί μόνο να ενημερώσουμε αυτήν την τάξη.
Πρόσθεσα doc$
, collection$
, create
, και delete
. Τυλίγουν τις μεθόδους @angular/fire
και παρέχουν καταγραφή όταν το Firebase μεταδίδει δεδομένα - αυτό θα γίνει πολύ βολικό για τον εντοπισμό σφαλμάτων - και αφού δημιουργηθεί ή διαγραφεί ένα αντικείμενο.
Η γενική μας υπηρεσία καταστήματος θα κατασκευαστεί χρησιμοποιώντας το RxJS ’BehaviorSubject
. BehaviorSubject
επιτρέπει στους συνδρομητές να λάβουν την τελευταία τιμή που εκπέμπεται μόλις εγγραφούν. Στην περίπτωσή μας, αυτό είναι χρήσιμο επειδή θα μπορέσουμε να ξεκινήσουμε το κατάστημα με μια αρχική τιμή για όλα τα στοιχεία μας όταν εγγραφούν στο κατάστημα.
Το κατάστημα θα έχει δύο μεθόδους, patch
και set
. (Θα δημιουργήσουμε μεθόδους get
αργότερα.)
Ας δημιουργήσουμε app/core/services/store.service.ts
:
import { BehaviorSubject, Observable } from 'rxjs'; import { environment } from 'src/environments/environment'; export abstract class StoreService { protected bs: BehaviorSubject; state$: Observable; state: T; previous: T; protected abstract store: string; constructor(initialValue: Partial) { this.bs = new BehaviorSubject(initialValue as T); this.state$ = this.bs.asObservable(); this.state = initialValue as T; this.state$.subscribe(s => { this.state = s }) } patch(newValue: Partial, 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, 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
. Αυτό θα μας επιτρέψει να εφαρμόσουμε τιμές μόνο σε ορισμένες ιδιότητες της πολιτείας. Ο κατασκευαστής θα εγγραφεί επίσης στο εσωτερικό BehaviorSubject
εκπομπές και να ενημερώνεται η εσωτερική κατάσταση μετά από κάθε αλλαγή.
patch()
θα λάβει το newValue
του τύπου Partial
και θα το συγχωνεύσει με το τρέχον this.state
αξία του καταστήματος. Τέλος, εμείς next()
το newState
και εκπέμψτε τη νέα κατάσταση σε όλους τους συνδρομητές του καταστήματος.
set()
λειτουργεί πολύ παρόμοια, μόνο αυτό αντί να διορθώνει την τιμή κατάστασης, θα το θέσει στο newValue
έλαβε.
Θα καταγράψουμε τις προηγούμενες και τις επόμενες τιμές της κατάστασης καθώς πραγματοποιούνται αλλαγές, οι οποίες θα μας βοηθήσουν να εντοπίσουμε τον εντοπισμό σφαλμάτων και να παρακολουθούμε εύκολα τις αλλαγές κατάστασης.
πώς να χρησιμοποιήσετε το junit στο intellij
Εντάξει, ας δούμε όλα αυτά σε δράση. Αυτό που θα κάνουμε είναι να δημιουργήσουμε μια σελίδα υπαλλήλων, η οποία θα περιέχει μια λίστα υπαλλήλων, καθώς και μια φόρμα για την προσθήκη νέων υπαλλήλων.
Ας ενημερώσουμε app.component.html
για να προσθέσετε μια απλή γραμμή πλοήγησης:
Στη συνέχεια, θα δημιουργήσουμε μια βασική ενότητα:
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
Στο employees-routing.module.ts
, ας προσθέσουμε το employees
Διαδρομή:
// ... import { EmployeesPageComponent } from './components/employees-page/employees-page.component'; // ... const routes: Routes = [ { path: 'employees', component: EmployeesPageComponent } ]; // ...
Και σε employees.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; }
Η υπηρεσία μας θα εμφανίζεται σε ένα αρχείο με την ονομασία employees/services/employee.firestore.ts
. Αυτή η υπηρεσία θα επεκτείνει τη γενική FirestoreService
δημιουργήθηκε στο παρελθόν και θα ορίσουμε απλώς το basePath
της συλλογής 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 { protected basePath: string = 'employees'; }
Στη συνέχεια, θα δημιουργήσουμε το αρχείο employees/states/employees-page.ts
. Αυτό θα χρησιμεύσει ως κατάσταση της σελίδας υπαλλήλων:
import { Employee } from '../models/employee'; export interface EmployeesPage { loading: boolean; employees: Employee[]; formStatus: string; }
Το κράτος θα έχει loading
τιμή που καθορίζει εάν θα εμφανιστεί ένα μήνυμα φόρτωσης στη σελίδα, το employees
οι ίδιοι, και ένα formStatus
μεταβλητή για τη διαχείριση της κατάστασης της φόρμας (π.χ. Saving
ή Saved
.)
Θα χρειαστούμε ένα αρχείο στο employees/services/employees-page.store.ts
. Εδώ θα επεκτείνουμε το StoreService
δημιουργήθηκε πριν. Θα ορίσουμε το όνομα του καταστήματος, το οποίο θα χρησιμοποιηθεί για την αναγνώρισή του κατά τον εντοπισμό σφαλμάτων.
Αυτή η υπηρεσία θα προετοιμάσει και θα κρατήσει την κατάσταση της σελίδας υπαλλήλων. Σημειώστε ότι ο κατασκευαστής καλεί 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 { 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 { return this.store.state$.pipe(map(state => state.loading ? [] : state.employees)) } get loading$(): Observable { return this.store.state$.pipe(map(state => state.loading)) } get noResults$(): Observable { return this.store.state$.pipe( map(state => { return !state.loading && state.employees && state.employees.length === 0 }) ) } get formStatus$(): Observable { 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
και employees
με την επιστρεφόμενη συλλογή της Firestore. Δεδομένου ότι έχουμε κάνει ένεση EmployeeFirestore
, τα αντικείμενα που επιστρέφονται από το Firestore πληκτρολογούνται σε Employee
, γεγονός που επιτρέπει περισσότερες δυνατότητες IntelliSense.
Αυτή η συνδρομή θα είναι ζωντανή ενώ η εφαρμογή είναι ενεργή, ακούει όλες τις αλλαγές και ενημερώνει το κατάστημα κάθε φορά που το Firestore μεταδίδει δεδομένα.
this.firestore.collection$().pipe( tap(employees => { this.store.patch({ loading: false, employees, }, `employees collection subscription`) }) ).subscribe()
Το employees$()
και loading$()
Οι συναρτήσεις θα επιλέξουν το κομμάτι της κατάστασης που θέλουμε να χρησιμοποιήσουμε αργότερα στο στοιχείο. employees$()
θα επιστρέψει έναν κενό πίνακα όταν φορτώνεται η κατάσταση. Αυτό θα μας επιτρέψει να εμφανίσουμε τα κατάλληλα μηνύματα στην προβολή.
get employees$(): Observable { return this.store.state$.pipe(map(state => state.loading ? [] : state.employees)) } get loading$(): Observable { return this.store.state$.pipe(map(state => state.loading)) }
Εντάξει, οπότε τώρα έχουμε όλες τις υπηρεσίες έτοιμες και μπορούμε να δημιουργήσουμε τα στοιχεία προβολής μας. Αλλά πριν το κάνουμε αυτό, μια γρήγορη ανανέωση μπορεί να είναι χρήσιμη…
async
ΣωλήναςΤα παρατηρήσιμα επιτρέπουν στους συνδρομητές να λαμβάνουν εκπομπές δεδομένων ως ροή. Αυτό, σε συνδυασμό με το async
σωλήνα, μπορεί πολύ ισχυρό.
Το async
Ο σωλήνας φροντίζει να εγγραφεί σε ένα παρατηρήσιμο και να ενημερώνει την προβολή όταν εκπέμπονται νέα δεδομένα. Το πιο σημαντικό, καταργεί αυτόματα την εγγραφή όταν το στοιχείο καταστρέφεται, προστατεύοντάς μας από διαρροές μνήμης.
Μπορείτε να διαβάσετε περισσότερα για τη βιβλιοθήκη Observables και RxJs γενικά τα επίσημα έγγραφα .
Στο employees/components/employees-page/employees-page.component.html
, θα βάλουμε αυτόν τον κωδικό:
Employees
Ομοίως, employees/components/employees-list/employees-list.component.html
θα το έχει αυτό, χρησιμοποιώντας το async
τεχνική σωλήνων που αναφέρεται παραπάνω:
Loading... No results {{employee.location}} {{employee.name}}
{{employee.hasDriverLicense ? 'Can drive': ''}}
Delete
Αλλά σε αυτήν την περίπτωση θα χρειαστούμε και έναν κωδικό TypeScript για το στοιχείο. Το αρχείο employees/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; employees$: Observable; noResults$: Observable; 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
το κατάστημα επιδιορθώθηκε, ρύθμιση loading
από true
έως false
.
Εντάξει, ας δημιουργήσουμε τη φόρμα για να προσθέσουμε νέους υπαλλήλους στο Firestore:
Σε employees/components/employees-form/employees-form.component.html
θα προσθέσουμε αυτόν τον κωδικό:
Name Please enter a Name. Choose location {{loc}} Please select a Location. Has driver license Add { status$ }
Ο αντίστοιχος κωδικός TypeScript θα εμφανίζεται σε 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 ; constructor( private employees: EmployeesService ) {} ngOnInit() { this.status$ = this.employees.formStatus$; } isInvalid(name) async submit() { this.form.disable() await this.employees.create({ ...this.form.value }) this.form.reset() this.form.enable() } }
Η φόρμα θα καλέσει το create()
μέθοδος EmployeesService
. Αυτή τη στιγμή η σελίδα μοιάζει με αυτήν:
Ας ρίξουμε μια ματιά στο τι συμβαίνει όταν προσθέτουμε έναν νέο υπάλληλο.
Μετά την προσθήκη ενός νέου υπαλλήλου, θα δούμε την ακόλουθη έξοδο στην κονσόλα:
Αυτά είναι όλα τα συμβάντα που ενεργοποιούνται κατά την προσθήκη ενός νέου υπαλλήλου. Ας ρίξουμε μια πιο προσεκτική ματιά.
Όταν καλούμε 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)
. Στην επιστροφή υποσχέσεων, ορίσαμε formStatus='Saved!'
και καταγραφή (5)
. Τέλος, ορίσαμε ένα χρονικό όριο για να ορίσετε formStatus
επιστροφή στο κενό, καταγραφή (6)
.
Καταγραφή συμβάντων (2)
και (3)
είναι τα συμβάντα που πυροδοτούνται από τη συνδρομή Firestore στη συλλογή υπαλλήλων. Όταν το EmployeesService
είναι instantiated, εγγραφούμε στη συλλογή και λαμβάνουμε τη συλλογή σε κάθε αλλαγή που συμβαίνει.
Αυτό ορίζει μια νέα κατάσταση στο κατάστημα με loading=false
ορίζοντας το employees
συστοιχία στους υπαλλήλους που προέρχονται από το Firestore.
Εάν επεκτείνουμε τις ομάδες καταγραφής, θα δούμε λεπτομερή δεδομένα για κάθε συμβάν και ενημέρωση του καταστήματος, με την προηγούμενη τιμή και την επόμενη, η οποία είναι χρήσιμη για τον εντοπισμό σφαλμάτων.
Έτσι φαίνεται η σελίδα μετά την προσθήκη ενός νέου υπαλλήλου:
Ας υποθέσουμε ότι τώρα θέλουμε να εμφανίσουμε ορισμένα συνοπτικά δεδομένα στη σελίδα μας. Ας υποθέσουμε ότι θέλουμε τον συνολικό αριθμό εργαζομένων, πόσοι είναι οδηγοί και πόσοι είναι από το Rosario.
ποια είναι η διαφορά μεταξύ s corp και c corp
Θα ξεκινήσουμε προσθέτοντας τις νέες ιδιότητες κατάστασης στο μοντέλο κατάστασης σελίδας στο employees/states/employees-page.ts
:
// ... export interface EmployeesPage { loading: boolean; employees: Employee[]; formStatus: string; totalEmployees: number; totalDrivers: number; totalRosarioEmployees: number; }
Και θα τα προετοιμάσουμε στο κατάστημα σε employees/services/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 { return this.store.state$.pipe(map(state => state.totalEmployees)) } get totalDrivers$(): Observable { return this.store.state$.pipe(map(state => state.totalDrivers)) } get totalRosarioEmployees$(): Observable { return this.store.state$.pipe(map(state => state.totalRosarioEmployees)) } // ...
Τώρα, ας δημιουργήσουμε το στοιχείο σύνοψης:
ng g c employees/components/EmployeesSummary
Θα το βάλουμε σε employees/components/employees-summary/employees-summary.html
:
Total: { async}
Drivers: { async}
Rosario: { async}
Και σε 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 ; drivers$: Observable ; rosario$: Observable ; constructor( private employees: EmployeesService ) {} ngOnInit() { this.total$ = this.employees.totalEmployees$; this.drivers$ = this.employees.totalDrivers$; this.rosario$ = this.employees.totalRosarioEmployees$; } }
Στη συνέχεια, θα προσθέσουμε το στοιχείο στο employees/employees-page/employees-page.component.html
:
// ... Employees
// ...
Το αποτέλεσμα είναι το ακόλουθο:
Στην κονσόλα έχουμε:
Η υπηρεσία υπαλλήλων υπολογίζει το σύνολο totalEmployees
, totalDrivers
, και totalRosarioEmployees
σε κάθε εκπομπή και ενημερώνει την κατάσταση.
ο Ο πλήρης κώδικας αυτού του σεμιναρίου είναι διαθέσιμος στο GitHub , και υπάρχει επίσης μια ζωντανή επίδειξη .
Σε αυτό το σεμινάριο, καλύψαμε μια απλή προσέγγιση για τη διαχείριση της κατάστασης σε εφαρμογές Angular χρησιμοποιώντας ένα Firebase back-end.
Αυτή η προσέγγιση ταιριάζει όμορφα με τις γωνιακές οδηγίες χρήσης των παρατηρήσιμων. Διευκολύνει επίσης τον εντοπισμό σφαλμάτων παρέχοντας παρακολούθηση για όλες τις ενημερώσεις στην κατάσταση της εφαρμογής.
Η γενική υπηρεσία καταστήματος μπορεί επίσης να χρησιμοποιηθεί για τη διαχείριση της κατάστασης εφαρμογών που δεν χρησιμοποιούν λειτουργίες Firebase, είτε για τη διαχείριση μόνο των δεδομένων της εφαρμογής ή των δεδομένων που προέρχονται από άλλα API.
Αλλά προτού το εφαρμόσετε αδιάκριτα, ένα πράγμα που πρέπει να λάβετε υπόψη είναι ότι EmployeesService
εγγράφεται στο Firestore στον κατασκευαστή και συνεχίζει να ακούει ενώ η εφαρμογή είναι ενεργή. Αυτό μπορεί να είναι χρήσιμο εάν χρησιμοποιούμε τη λίστα υπαλλήλων σε πολλές σελίδες της εφαρμογής, για να αποφύγουμε τη λήψη δεδομένων από το Firestore κατά την πλοήγηση μεταξύ των σελίδων.
Ωστόσο, αυτή δεν μπορεί να είναι η καλύτερη επιλογή σε άλλα σενάρια, όπως εάν απλά χρειάζεται να τραβήξετε αρχικές τιμές μία φορά και, στη συνέχεια, να ενεργοποιήσετε χειροκίνητα τις επαναφορτώσεις δεδομένων από το Firebase. Η ουσία είναι ότι είναι πάντα σημαντικό να κατανοήσετε τις απαιτήσεις της εφαρμογής σας για να επιλέξετε καλύτερες μεθόδους εφαρμογής.
Το Angular (αρχικά AngularJS) είναι ένα δημοφιλές πλαίσιο front-end για τη δημιουργία εφαρμογών μιας σελίδας (SPA). Είναι ανοιχτού κώδικα και υποστηρίζεται από την Google.
Η διαχείριση της κατάστασης αφορά τη σωστή παρακολούθηση μεταβλητών σε μια εφαρμογή ιστού. Π.χ. εάν ένας χρήστης της εφαρμογής συνομιλίας άλλαξε αίθουσες συνομιλίας, αυτή είναι μια αλλαγή στην κατάσταση. Εάν έστειλαν τότε ένα μήνυμα, αλλά η δυνατότητα αποστολής δεν γνώριζε την προηγούμενη αλλαγή κατάστασης, θα έστελνε το μήνυμα στην προηγούμενη αίθουσα συνομιλίας, με αποτέλεσμα ένα πολύ κακό UX.
Το Google Firebase είναι μια πλατφόρμα ανάπτυξης εφαρμογών για κινητά all-in-one. Είναι γνωστό για την αρχική προσφορά βάσης δεδομένων σε πραγματικό χρόνο, αλλά σήμερα περιλαμβάνει ενσωματωμένες αναφορές σφαλμάτων, έλεγχο ταυτότητας και φιλοξενία στοιχείων, μεταξύ άλλων.