Hvordan man laver CRUD i Angular + Firebase Firestore

CRUD – Create, Read, Update, Delete – de fire hellige gral af databasehandlinger. De er de eneste ting, du nogensinde får brug for for at gøre noget væsentligt med din database. Selvfølgelig kan du øge kompleksiteten af forespørgslerne, men i sidste ende koger det hele ned til disse fire handlinger.

Firebase er et Google-ejet cloud-baseret system, der leveres komplet med API-kroge, filopbevaringsplads, auth-system og hostingfunktioner. Det er et meget undervurderet system, der bør udnyttes mere til prototyping og hurtig applikationsudvikling.

Hvis du ønsker at starte en progressiv webapp op, men ikke har backend-erfaring med opsætning af servere, oprettelse af API’er og håndtering af databaser, så er Firebase en fantastisk mulighed for frontend-udviklere, der måske føler sig isoleret og fastlåst af den massive bakke af oplysninger, de skal behandle for overhovedet at få deres app op at køre.

Og hvis du bare mangler tid, kan Firebase reducere dine udviklingstimer næsten til det halve, så du kan fokusere på brugeroplevelsen og implementere disse UI-flows. Det er også fleksibelt nok til at migrere din frontend-applikation ud og bruge en anden database, hvis det er nødvendigt.

Her er en hurtig vejledning om, hvordan du implementerer CRUD-handlinger med Angular og Firebase.

Det er en bare bones kaffebestillingsapp, hvor du kan tilføje ordrer (oprette), liste ordrer fra databasen (læse), markere ordren som afsluttet (opdatere) og fjerne en ordre (slette).

Sigtet med denne vejledning er at hjælpe dig med at komme i gang med Firebase Firestore og se, hvor nemt det er at oprette forbindelse til og komme i gang med den Google-ejede tjeneste. Dette er ikke en reklame for Google (jeg får ingen kickbacks fra dem for dette), men blot en illustration af, hvordan Angular spiller sammen med databasen.

Den app, vi skal lave, er ikke perfekt. Der mangler ting som datavalidering og en million mulige funktioner, som jeg også kan tilføje. Men det er ikke det, det drejer sig om. Pointen er at sætte op i Angular så hurtigt som muligt og få den til at fungere med en levende database.

Så, nok af introen – her er koden walk through.

Den indledende opsætning

Sæt din Angular-app op via Angular CLI’en. Hvis du ikke allerede har Angular CLI, kan du læse mere om det her.

Kort sagt skal du blot køre disse kommandoer i din terminal i den mappe, hvor du vil have din Angular-app til at ligge. Her er kommandoerne, og hvad de gør.

npm install -g @angular/cli

Installerer Angular CLI i din terminal, hvis du ikke allerede har det.

ng new name-of-app-here

Dette vil oprette et nyt Angular-projekt med den nyeste version af Angular, der er tilgængelig.

cd name-of-app-here

Når du opretter et nyt projekt via Angular CLI, vil den oprette en ny projektmappe for dig. cd vil føre dig ind i denne mappe via terminalen.

ng serve

Dette er vil starte og køre dit Angular-projekt.

Opsætning af Angular

Vi vil oprette 3 nye dele i alt til denne Angular-app – ordrer, ordre-liste og shared/ordersService. De to første er komponenter, der vil indeholde grænsefladen til appen og shared/orders service, som vil holde alle Firebase API-opkald samlet.

For at oprette de nødvendige filer skal du køre følgende kommandoer:

ng g c orders

ng g c orders

g står for generate og c står for component. Den sidste del af kommandoen er navnet på din fil, som i ovenstående tilfælde hedder orders. Du kan også bruge ng generate component orders for at opnå samme effekt.

Opret en anden komponent til order-list ved hjælp af følgende kommando:

ng g c order-list

Og endelig skal du for tjenesten bruge s i stedet for c som nedenfor:

ng g s shared/orders

Dette vil oprette en orders.service.ts-fil i en mappe med navnet shared. Sørg for at tilføje orders.service.ts i app.module.ts, da dette ikke gøres automatisk for dig, som det er tilfældet med komponenterne. Du kan gøre dette via import og tilføje det til providers-listen på følgende måde:

import { OrdersService } from "./shared/orders.service";
...
providers:
...

CSS

I denne vejledning vil vi bruge Materialize CSS til at få vores endelige applikation til at se bedre ud end standard-css’en. Det er ikke nødvendigt, og du kan bruge hvilken som helst CSS-ramme eller din egen brugerdefinerede CSS, hvis du vil. Du kan se detaljerne om Materialize CSS her.

Vi vil også bruge Googles materialeikoner som knapper til at markere kaffebestillingen som afsluttet eller slette bestillingen.

En måde at implementere dette på er at have nedenstående kode lige over </head>-tagget i din index.html-fil, der ligger i src-mappen.

<!-- Compiled and minified Materialize CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<!-- Compiled and minified Materialize JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script><!-- Google Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />

Den indledende Angular view-kode

I app.component.html skal du slette al startkoden. Vi skal bruge vores eget indhold til dette.

Vi kobler os på den komponent, vi lige har oprettet, og viser dem på skærmen ved hjælp af selektorerne app-orders og app-order-list. For at gøre dette skriver vi nedenstående kode:

<div class="container">
<div class="row">
<app-orders class="col s6"></app-orders>
<app-order-list class="col s6"></app-order-list>
</div>
</div>

Klasserne container, row, col og s6 er en del af Materialize CSS-gittersystemet. Alle klasser, som du vil se i resten af denne vejledning, er fra Materialize CSS, medmindre andet er nævnt.

Opsætning af formular

For at opsætte formularer skal du importere ReactiveFormsModule i app.module.ts og huske at importere den i imports-arrayet.

import { ReactiveFormsModule } from "@angular/forms";

Ind i orders.service.ts importer FormControl og FormGroup fra @angular/formsog opret en ny formular uden for constructor, hvor du kan indstille formularens egenskaber som nedenfor:

import { FormControl, FormGroup } from "@angular/forms";
...
...export class OrdersService {
constructor() {}
form = new FormGroup({
customerName: new FormControl(''),
orderNumber: new FormControl(''),
coffeeOrder: new FormControl(''),
completed: new FormControl(false)
})
}

Vi skal bruge disse værdier til at gemme i Firebase lidt senere i denne tutorial.

Brug formularen inde i en komponent

import OrdersService-klassen i din komponent, så du kan bruge objektet inde i din komponent og derefter oprette et objekt af klassen inde i constructor.

import { OrdersService } from "../shared/orders.service";
...
constructor(private ordersService:OrdersService){}

Inde i din orders.component.html skal du bruge formGroup-direktivet til at referere til det formularobjekt, der er oprettet i OrdersService. Hver formControlName refererer til de navne, som vi har brugt i formGroup, der er oprettet inde i OrdersService-klassen. Dette vil gøre det muligt for den tilknyttede controller at bruge de variabler, der er indtastet i formularen. Se koden nedenfor:

<form ="this.ordersService.form">
<input placeholder="Order Number"
formControlName="orderNumber"
type="text"
class="input-field col s12">
<input placeholder="Customer Name"
formControlName="customerName"
type="text"
class="input-field col s12">
</form>

I orders.component.ts skal vi oprette et array af kaffe til at løbe igennem på vores orders.component.html. I teorien kunne du også opsætte dette inde i din Firestore-database, lave et kald til samlingen og derefter bruge den. Men af hensyn til længden vil vi opsætte det som et lokalt array. Inde i din OrdersComponent-klasse opstiller du følgende array:

coffees = ;

I din orders.component.html og inde i dine <form>-tags løber du gennem det ved hjælp af *ngFor med en (click) action handler for at tilføje nye kaffer til din bestilling. Vi vil vise bestillingslisten lige nedenfor med mulighed for at fjerne den enkelte kaffe som følger:

<button class="waves-effect waves-light btn col s4" 
*ngFor="let coffee of coffees"
(click)="addCoffee(coffee)">
{{coffee}}
</button><ul class="collection">
<li *ngFor="let coffee of coffeeOrder">
<span class="col s11"> {{ coffee }} </span>
<a class="col s1" (click)="removeCoffee(coffee)">x</a>
</li>
</ul>

Inden for orders.component opretter du et tomt array til at rumme kaffebestillingen, bruger funktionen addCoffee() til at tilføje nye kaffer og removeCoffee() til at fjerne en drik fra din bestillingsliste.

coffeeOrder = ;addCoffee = coffee => this.coffeeOrder.push(coffee);removeCoffee = coffee => {
let index = this.coffeeOrder.indexOf(coffee);
if (index > -1) this.coffeeOrder.splice(index, 1);
};

Håndtering af formularindsendelse

Føj et input Submit Order ind i og i bunden af <form>-tags og tilføj onSubmit() til en klikhåndtering som nedenfor:

<form ="this.ordersService.form" (ngSubmit)="onSubmit()"> ...
<button
class="waves-effect waves-light btn col s12"
(click)="onSubmit()">
Submit
</button>
</form>

Opret en tom onSubmit funktion i din orders.component til den mellemliggende onSubmit funktion. Vi vil snart tilføje hændelseshåndtering til den. På dette tidspunkt bør din formular være klar til at blive koblet op til din Firebase-database.

Opsætning af listekomponenten

Nu mangler vi bare et sted at vise vores kaffebestillinger fra databasen. Indtil videre vil vi bare opsætte stilladset html.

Navigér til din order-list.component.html og opret en tabel med 3 overskrifter og 5 dataceller. De første 3 dataceller vil indeholde de værdier, der trækkes fra databasen, og de sidste 2 vil indeholde ekstra funktionalitet, der giver dig mulighed for at markere ordren som fuldført eller slette ordren.

Indstilling af Firebase

Gå til din Firebase-konsol, og tilføj et nyt projekt.

Klik på ‘Tilføj projekt’ for at oprette et nyt projekt.

Føj et projektnavn til, accepter vilkårene og betingelserne, og klik på opret projekt.

Giv dit projekt et navn, og accepter controller-controller-betingelserne for at oprette et projekt.

Når dit Firebase-projekt er oprettet, vil du se noget, der ligner dette.

Opret en database ved at vælge Database på sidepanelet (placeret under Develop) og klik derefter på Create Database under Cloud Firestore-banneret.

Klik på ‘Create database’

Vælg start i testtilstand for sikkerhedsregler. Du kan ændre det senere.

Husk at opbevare dine legitimationsoplysninger kun på din lokale maskine af sikkerhedshensyn.

Du vil få en tom Firestore-database som denne.

Forbindelse af Firebase Firestore til Angular

For at forbinde din Angular-app til Firebase skal du installere firebase og @angular/fire-pakker. Dette vil give dig adgang til AngularFireModule og AngularFirestoreModule. Brug følgende kommando i din terminal til at installere dem.

npm i --save firebase @angular/fire

Implementering af konnektorer

Gå tilbage til din Firebase-webkonsol, og hent konfigurationsoplysningerne til brug i din Angular-app. Dette er placeret på din projektoversigtsside. Den ser nogenlunde sådan ud:

Du skal bruge dine egne legitimationsoplysninger. Ovenstående config vil ikke fungere for dig.

Kopier den fremhævede del af koden, naviger til din environment.ts-fil (placeret i environments-mappen), og indsæt konfigurationsoplysningerne i environment-objektet som firebaseConfig. Se nedenstående eksempel:

export const environment = {
production: false,
firebaseConfig: {
apiKey: "xxx",
authDomain: "xxxx.firebaseapp.com",
databaseURL: "https://xxxx.firebaseio.com",
projectId: "xxxx",
storageBucket: "xxxx.appspot.com",
messagingSenderId: "xxxxx"
}
};

Importér de pakker, du installerede tidligere, og environment.ts-filen i din app.module.ts.Du skal initialisere AngularFireModule med den firebaseConfig, som du lige har foretaget opsætningen for. Se eksemplet nedenfor:

import { environment } from "src/environments/environment";
import { AngularFireModule } from "@angular/fire";
import { AngularFirestoreModule } from "@angular/fire/firestore";
...
@NgModule({
...
imports:
...
})

Opsætning af Firestore i en tjeneste

Importer AngularFirestore i din orders.service.ts-fil og deklarer den i konstruktøren, så din tjeneste kender den.

...
import { AngularFirestore } from '@angular/fire/firestore';
...
export class OrdersService {
constructor( private firestore: AngularFirestore ) {}
...
}

Nu er du helt klar og klar til at gå i gang med CRUD-delen af denne Angular + Firebase Firestore-tutorial.

C står for Create

For at oprette en ny post i din helt spritnye Firestore-database skal du kalde .add() . Vi gør dette inde i orders.service.ts-filen.

For at gøre dette skal du angive collection-navnet, og hvilke data du ønsker at sende til databasen. I vores tilfælde er det coffeeOrders .

Eksempelkoden nedenfor bruger et løfte til at returnere Firebase-opkaldet og lader dig derefter bestemme, hvad du vil gøre, når det hele er gjort.

...
createCoffeeOrder(data) {
return new Promise<any>((resolve, reject) =>{
this.firestore
.collection("coffeeOrders")
.add(data)
.then(res => {}, err => reject(err));
});
}
...

For at kalde denne funktion i din komponent skal du navigere til orders.component.ts og inde i onSubmit()-funktionen og handlingshåndteringen foretage et kald createCoffeeOrder() gennem ordersService.

Eksemplet nedenfor foretager en vis behandling af dataene ved at mappe coffeeOrder-arrayet til formularværdien coffeeOrder. Jeg har også oprettet en data-variabel til destruktureringsformål (sammen med at jeg ikke behøver at indsætte et enormt langt navn i createCoffeeOrder()).

...
onSubmit() {
this.ordersService.form.value.coffeeOrder = this.coffeeOrder;
let data = this.ordersService.form.value;
this.ordersService.createCoffeeOrder(data)
.then(res => {
/*do something here....
maybe clear the form or give a success message*/
});
}
...

Og viola! Du har nu oprettet en post fra din localhost Angular-app til din Firestore-database.

R står for Read

For at kunne vise dine data om kaffebestillinger skal du bruge en read-funktion i din orders.service.ts. Det følgende kodeeksempel vil give dig alle de værdier, der er gemt i din coffeeOrders-samling.

...
getCoffeeOrders() {
return
this.firestore.collection("coffeeOrders").snapshotChanges();
}
...

For at bruge denne funktion skal du kalde den fra din orders-list.component.ts . Det kan du gøre ved at importere OrdersService i filen og initialisere den i i constructor.

...
import { OrdersService } from "../shared/orders.service";
...export class OrderListComponent implements OnInit {
constructor(private ordersService:OrdersService){}
...
}

Opret en funktion til at indeholde dit kald og initialiser den på ngOnInit() for at kalde den, når visningen indlæses første gang. Opret en coffeeOrders-variabel til at mappe de returnerede resultater fra din database via subscribe(). Vi vil bruge dette til at iterere over og vise i order-list.component.html

...
ngOnInit() {this.getCoffeeOrders();}
... coffeeOrders; getCoffeeOrders = () =>
this.ordersService
.getCoffeeOrders()
.subscribe(res =>(this.coffeeOrders = res));...

For at bruge coffeeOrders i din order-list.component.html , skal du bruge *ngFor til at løbe gennem det returnerede array. Du skal også gøre lidt af inception og lave en anden løkke for coffeeOrder-delen for at få din liste over kaffer for hver kunde. Der findes mere effektive måder at gøre dette på, men i denne tutorial kan du se nedenstående eksempelkode:

...
<tbody>
<tr *ngFor="let order of coffeeOrders">
<td>{{ order.payload.doc.data().orderNumber }}</td>
<td>{{ order.payload.doc.data().customerName }}</td>
<td><span
*ngFor="let coffee of order.payload.doc.data().coffeeOrder">
{{ coffee }}
</span>
</td>
</tr>
</tbody>
...

Og der har du det. Du har nu tilsluttet læsefunktioner til din Angular-applikation.

U står for Update

Lad os sige, at vi vil være i stand til at markere kaffebestillingen som komplet i databasen og gøre noget ved posten baseret på ændringen. Fordi snapshot() holder styr på alle ændringer, der sker, behøver du ikke at foretage nogen sporing eller polling til databasen.

Vi opretter en anden datacelle i vores tabel med et “check”-ikon fra Google Materialize Icons og kobler den til en (click)-hændelse, der kalder funktionen markCompleted() i din order-list.component.ts. Vi vil også videregive den særlige rækkefølge for denne sløjfe.

Vi vil indstille en -attribut med completed-værdien til datacellen, så den dynamisk kan bestemme, om vi ønsker at have ‘check’-ikonet på displayet. Vi har oprindeligt indstillet denne værdi som false, da vi først oprettede formularen i orders.service.ts .

...
<td ="order.payload.doc.data().completed"
(click)="markCompleted(order)">
<i class="material-icons">check</i>
</td>
...

Inside order-list.component.ts , opretter funktionen markCompleted() , der bruger den injicerede data til at videregive til en funktion kaldet updateCoffeeOrder() i orders.service.ts.

...
markCompleted = data =>
this.ordersService.updateCoffeeOrder(data);
...

Inside orders.service.ts opretter håndteringsfunktionen. Denne funktion vil oprette forbindelse til og kalde din Firestore-database baseret på den valgte samling og dokument-id. Vi ved allerede, at vores samling hedder coffeeOrders, og vi kan finde dokument-id’et ud fra de parametre, der er overført fra komponentfunktionskaldet.

.set() indstiller den specifikke post med de data, du har overført. .set() tager to parametre ind – dine data og et indstillingsobjekt. Hvis du bruger merge: true, betyder det, at du kun opdaterer det værdi-nøglepar, der er overdraget, i stedet for at erstatte hele dokumentet med det, du har overdraget.

...
updateCoffeeOrder(data) {
return
this.firestore
.collection("coffeeOrders")
.doc(data.payload.doc.id)
.set({ completed: true }, { merge: true });
}
...

Nu, når du klikker på ikonet “check” i din visning, vil det opdatere din database og forsvinde, fordi din -attribut nu er indstillet til true.

D står for Delete

For den sidste handling skal vi opsætte det hele på samme måde som opdateringsprocessen – men i stedet for at opdatere posten skal vi slette den.

Sæt en anden datacelle op med en klikhændelseshåndtering, der kalder en deleteOrder()-funktion. Vi vil sende instansen af order data ind i sløjfen, når der bliver klikket på ikonet “delete_forever”. Du får brug for dette til dokument-id’et. Se nedenstående kode for eksempel:

...
<td ="order.payload.doc.data().completed"
(click)="deleteOrder(order)">
<i class="material-icons">delete_forever</i>
</td>
...

Inden for din order-list.component.ts opretter du den tilhørende funktion, der kalder en deleteCoffeeOrder()-funktion inde i din orders.service.ts .

...
deleteOrder = data => this.ordersService.deleteCoffeeOrder(data);
...

Inden for din orders.service.ts-fil opretter du deleteCoffeeOrder()-funktionen og bruger data-injektet til at finde ud af, hvad dokument-id’et er. Ligesom ved opdatering skal du kende både opsamlingsnavnet og dokument-id’et for at kunne identificere korrekt, hvilken post du vil slette. Brug .delete() til at fortælle Firestore, at du vil slette posten.

...
deleteCoffeeOrder(data) {
return
this.firestore
.collection("coffeeOrders")
.doc(data.payload.doc.id)
.delete();
}
...

Nu, når du klikker på ikonet “delete_forever”, vil din Angular-app fyre op og fortælle Firestore, at den specifikke post skal slettes. Din post forsvinder fra visningen, når det angivne dokument slettes.

Den endelige applikation

Du kan finde Github-repositoriet for hele det fungerende projekt her. Du skal oprette din egen Firebase-database og tilslutte den selv ved at opdatere konfigurationsfilerne. Selve koden er ikke perfekt, men jeg har holdt den meget minimal, så du kan se, hvordan Firestore fungerer i en Angular-app uden at skulle gå igennem en jungle af kode.

Jeg har forsøgt at komprimere tutorialen så meget som muligt uden at gå glip af detaljer. Det var en hård blanding, men som du kan se ovenfor, er meget af det mest Angular-koden og ikke meget Firestore. Firebase Firestore kan lave meget mere komplekse forespørgsler, men til demonstrationsformål har jeg holdt det simpelt. I teorien kan du, hvis din datastruktur forbliver den samme, udskifte Firestore og sætte et andet sæt API-forbindelser ind med minimal refaktorering påkrævet.

Slutord

Jeg kan personligt godt lide Firebase på grund af, hvor nemt det er at skabe hurtigt produktionsklare projekter uden at skulle oprette en hel backend og server til at understøtte det. Omkostningsmæssigt er det ikke så slemt, og testprojekter er gratis at starte op (og de får dig ikke til at indtaste dine kreditkortoplysninger, før du er klar til at skifte abonnement). Alt i alt tog det mere tid at oprette formularen og grænsefladen omkring appen end selve det at forbinde applikationen med Firestore. Jeg har ikke tilføjet formularvalidering til denne tutorial på grund af længden, men vil oprette et andet indlæg om det i fremtiden.

Skriv en kommentar