Hur man gör CRUD i Angular + Firebase Firestore

CRUD – Skapa, Läs, Uppdatera, Ta bort – de fyra heliga graalerna för databasåtgärder. Det är de enda saker du någonsin kommer att behöva för att göra något viktigt med din databas. Visst kan du öka frågornas komplexitet, men i slutändan är det dessa fyra åtgärder som gäller.

Firebase är ett molnbaserat system som ägs av Google och som levereras komplett med API-krokar, fillagringsutrymme, auth-system och värdskap. Det är ett mycket underskattat system som borde utnyttjas mer för prototyper och snabb applikationsutveckling.

Om du vill starta upp en progressiv webbapplikation men inte har backend-erfarenhet av att sätta upp servrar, skapa API:er och hantera databaser, så är Firebase ett fantastiskt alternativ för front-end-utvecklare som kan känna sig isolerade och fastna i den enorma kullen av information som de måste bearbeta för att ens få igång sin app.

Och om du bara har ont om tid kan Firebase minska dina utvecklingstimmar nästan till hälften så att du kan fokusera på användarupplevelsen och implementera dessa UI-flöden. Det är också tillräckligt flexibelt för att du ska kunna migrera ut din front end-applikation och använda en annan databas vid behov.

Här finns en snabbguide om hur du implementerar CRUD-åtgärder med Angular och Firebase.

Det är en enkel kaffebeställningsapp där du kan lägga till beställningar (skapa), lista beställningar från databasen (läsa), markera beställning som slutförd (uppdatera) och ta bort en beställning (ta bort).

Syftet med den här handledningen är att hjälpa dig att komma igång med Firebase Firestore och se hur enkelt det är att ansluta till och komma igång med den Googleägda tjänsten. Detta är inte en reklam för Google (jag får inga kickbacks från dem för detta) utan bara en illustration av hur Angular spelar med databasen.

Appen vi ska göra är inte perfekt. Det saknas saker som datavalidering och en miljon möjliga funktioner som jag också kan lägga till. Men det är inte det som är poängen. Poängen är att sätta upp i Angular så snabbt som möjligt och få den att fungera med en levande databas.

Så, nog med introduktionen – här kommer koden att gå igenom.

Den inledande inställningen

Sätt upp din Angular-app via Angular CLI. Om du inte redan har Angular CLI kan du läsa mer om det här.

Som kort sagt, kör helt enkelt dessa kommandon i din terminal i den katalog där du vill att din Angular-app ska ligga. Här är kommandona och vad de gör.

npm install -g @angular/cli

Installerar Angular CLI i din terminal om du inte redan har det.

ng new name-of-app-here

Detta kommer att skapa ett nytt Angular-projekt med hjälp av den senaste versionen av Angular som finns tillgänglig.

cd name-of-app-here

När du skapar ett nytt projekt via Angular CLI skapas en ny projektmapp åt dig. cd tar dig in i den mappen via terminalen.

ng serve

Detta är kommer att starta och köra ditt Angular-projekt.

Installation av Angular

Vi kommer att skapa 3 nya delar totalt för den här Angular-appen – orders, order-list och shared/ordersService. De två första är komponenter som kommer att innehålla gränssnittet för appen och shared/orders service som kommer att hålla ihop alla Firebase API-anrop.

För att skapa de nödvändiga filerna kör du följande kommandon:

ng g c orders

g står för generate och c står för component. Den sista delen av kommandot är namnet på din fil, som i fallet ovan heter orders. Du kan också använda ng generate component orders för att uppnå samma effekt.

Skapa en annan komponent för order-list med följande kommando:

ng g c order-list

Och slutligen, för tjänsten, använd s i stället för c som nedan:

ng g s shared/orders

Detta kommer att skapa en orders.service.ts-fil i en mapp med namnet shared. Var noga med att lägga till orders.service.ts i app.module.ts eftersom detta inte görs automatiskt för dig som i komponenterna. Du kan göra detta via import och lägga till den i providers-listan på följande sätt:

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

CSS

För den här handledningen kommer vi att använda Materialize CSS för att få vår slutliga applikation att se bättre ut än standard-css. Det är inte nödvändigt och du kan använda vilket CSS-ramverk som helst eller din egen anpassade CSS om du vill. Du kan läsa mer om Materialize CSS här.

Vi kommer också att använda Googles materialikoner som knappar för att markera kaffebeställningen som slutförd eller ta bort beställningen.

Ett sätt att implementera detta är att ha koden nedan precis ovanför </head>-taggen i din index.html-fil som ligger i mappen src.

<!-- 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 initiala Angular view-koden

I app.component.html tar du bort all startkod. Vi kommer att använda vårt eget innehåll för detta.

Vi kopplar in oss på den komponent vi just har skapat och visar dem på skärmen med hjälp av selektorerna app-orders och app-order-list. För att göra detta skriver vi koden nedan:

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

Klasserna container, row, col och s6 är en del av Materialize CSS-gridsystem. Alla klasser som du kommer att se i resten av den här handledningen är från Materialize CSS, om inget annat nämns.

Inställning av formuläret

För att ställa in formulär importerar du ReactiveFormsModule i app.module.ts och kommer ihåg att importera den i arrayen imports.

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

Inom orders.service.ts importera FormControl och FormGroup från @angular/formsoch skapa ett nytt formulär utanför constructor där du kan ställa in egenskaperna för formuläret enligt nedan:

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 kommer att använda dessa värden för att lagra i Firebase lite senare i den här handledningen.

Användning av formuläret inuti en komponent

import klassen OrdersService i din komponent så att du kan använda objektet inuti din komponent och sedan skapa ett objekt av klassen inuti constructor.

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

Inuti din orders.component.html använder du formGroup-direktivet för att referera till formulärobjektet som skapats i OrdersService. Varje formControlName hänvisar till de namn som vi använde i formGroup som skapades inuti klassen OrdersService. Detta gör det möjligt för den associerade styrenheten att använda de variabler som skrivs in i formuläret. Se koden nedan:

<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 kommer vi att skapa en array off coffee för looping genom på vår orders.component.html. I teorin skulle du också kunna ställa in detta i din Firestore-databas, göra ett anrop till samlingen och sedan använda den. Men av längdskäl kommer vi att konfigurera den som en lokal array. I din OrdersComponent-klass konfigurerar du följande array:

coffees = ;

I dina orders.component.html och <form>-taggar loopar du igenom den med hjälp av *ngFor med en (click)-åtgärdshandläggare för att lägga till nya kaffesorter i din beställning. Vi kommer att visa beställningslistan precis nedanför med möjlighet att ta bort enskilda kaffe enligt följande:

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

Inuti orders.component skapar du en tom array för att hysa kaffebeställningen, använder funktionen addCoffee() för att lägga till nya kaffe och removeCoffee() för att ta bort en dryck från din beställningslista.

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

Hantering av inlämning av formulär

Lägg till en ingång Submit Order inuti och längst ner i <form>-taggarna och lägg till onSubmit() till en klickhanterare som nedan:

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

Skapa en tom onSubmit funktion i din orders.component för mellantiden. Vi kommer snart att lägga till händelsehantering i den. Vid det här laget bör ditt formulär vara redo att anslutas till din Firebase-databas.

Inställning av listningskomponenten

Nu behöver vi bara ett utrymme för att visa våra kaffebeställningar från databasen. För tillfället ska vi bara sätta upp scaffold html.

Navigera till din order-list.component.html och skapa en tabell med 3 rubriker och 5 dataceller. De första 3 datacellerna kommer att innehålla de värden som hämtas från databasen och de sista 2 kommer att innehålla extra funktionalitet som gör att du kan markera beställningen som komplett eller ta bort beställningen.

Installation av Firebase

Gå till din Firebase-konsol och lägg till ett nytt projekt.

Klicka på ”Add Project” (Lägg till projekt) för att skapa ett nytt projekt.

Lägg till ett projektnamn, acceptera villkoren och klicka på skapa projekt.

Giv ditt projekt ett namn och acceptera villkoren för controller-controller för att skapa ett projekt.

När ditt Firebase-projekt har skapats kommer du att se något liknande.

Skapa en databas genom att välja Database på sidopanelen (som ligger under Develop) och klicka sedan på Create Database (Skapa databas) under Cloud Firestore-bannern.

Klicka på ”Create database”

Välj start in test mode (Starta i testläge) för säkerhetsregler. Du kan ändra det senare.

Håll dina autentiseringsuppgifter endast på din lokala dator av säkerhetsskäl.

Du får en tom Firestore-databas så här.

Anslutning av Firebase Firestore till Angular

För att ansluta din Angular-app till Firebase måste du installera paketen firebase och @angular/fire. Detta ger dig tillgång till AngularFireModule och AngularFirestoreModule. Använd följande kommando i din terminal för att installera dem.

npm i --save firebase @angular/fire

Implementering av anslutningarna

Gå tillbaka till din Firebase-webbkonsol och hämta konfigurationsdetaljerna för att använda dem i din Angular-app. Den finns på din projektöversiktssida. Den ser ut ungefär så här:

Du måste använda dina egna autentiseringsuppgifter. Konfigurationen ovan kommer inte att fungera för dig.

Kopiera det markerade avsnittet av koden, navigera till din environment.ts-fil (som ligger i environments-mappen) och klistra in konfigurationsuppgifterna i environment-objektet som firebaseConfig. Se exempel nedan:

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"
}
};

Importera paketen som du installerade tidigare och environment.ts-filen till din app.module.ts.Du måste initialisera AngularFireModule med firebaseConfig som du just har gjort konfigurationen för. Se exemplet nedan:

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

Inställning av Firestore i en tjänst

Import AngularFirestore till din orders.service.ts-fil och deklarera den i konstruktören så att din tjänst känner till den.

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

Nu är du redo att sätta igång med CRUD-delen av denna Angular + Firebase Firestore-tutorial.

C står för Create

För att skapa en ny post i din helt nya Firestore-databas måste du anropa .add() . Vi kommer att göra detta i orders.service.ts-filen.

För att göra detta måste du ange collection-namnet och vilka data du vill skicka till databasen. I vårt fall är det coffeeOrders .

Exempelkoden nedan använder ett löfte för att returnera Firebase-anropet och låter dig sedan bestämma vad du vill göra när allt är klart.

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

För att anropa den här funktionen i din komponent navigerar du till orders.component.ts och inne i onSubmit()-funktionen och handlingshanteraren gör du ett anrop till createCoffeeOrder() genom ordersService.

Exemplet nedan gör en viss bearbetning av data genom att mappa coffeeOrder-matrisen till formulärvärdet coffeeOrder. Jag har också skapat en data-variabel för destrukturering (och för att slippa skicka in ett enormt långt namn 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*/
});
}
...

Och viola! Du har nu skapat en post från din Angular-app på ditt lokala värd till din Firestore-databas.

R står för Read

För att kunna visa data om dina kaffebeställningar behöver du en read-funktion i din orders.service.ts. Följande kodexempel ger dig alla värden som finns lagrade i din coffeeOrders samling.

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

För att använda den här funktionen måste du anropa den från din orders-list.component.ts . Du kan göra detta genom att importera OrdersService till filen och initialisera den i i constructor.

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

Skapa en funktion som innehåller ditt anrop och initialisera den i ngOnInit() för att anropa den när vyn laddas för första gången. Skapa en coffeeOrders-variabel för att mappa de returnerade resultaten från din databas via subscribe(). Vi kommer att använda detta för att iterera över och visa i order-list.component.html

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

För att använda coffeeOrders i din order-list.component.html , använd *ngFor för att slinga genom den returnerade matrisen. Du måste också göra lite av inception och göra en annan slinga för coffeeOrder-delen för att få fram din lista över kaffesorter för varje kund. Det finns effektivare sätt att göra detta men för den här handledningen, se exempelkoden nedan:

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

Och där har du det. Du har nu kopplat upp läsfunktioner till din Angular-applikation.

U står för Update

Vi kan säga att vi vill kunna markera kaffebeställningen som komplett i databasen och göra något med posten baserat på ändringen. Eftersom snapshot() håller reda på alla ändringar som sker behöver du inte göra någon spårning eller polling till databasen.

Vi kommer att skapa en annan datacell i vår tabell med en ”check”-ikon från Google Materialize Icons och koppla den till en (click)-händelse som anropar funktionen markCompleted() i din order-list.component.ts. Vi kommer också att skicka in den särskilda ordningen för den här slingan.

Vi kommer att ställa in ett -attribut med completed-värdet till datacellen så att den dynamiskt kan avgöra om vi vill ha ”check”-ikonen på displayen. Vi har ursprungligen ställt in det här värdet som false när vi först skapade formuläret 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 , skapa funktionen markCompleted() som använder det injicerade data för att skicka till en funktion som heter updateCoffeeOrder() i orders.service.ts.

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

Inside orders.service.ts skapa hanteringsfunktionen. Den här funktionen ansluter och anropar din Firestore-databas baserat på den valda samlingen och dokument-ID:t. Vi vet redan att vår samling heter coffeeOrders och vi kan hitta dokument-ID:t baserat på de parametrar som skickas in från komponentfunktionsanropet.

.set() kommer att ställa in den specifika posten med de data du skickade in. .set() tar in två parametrar – dina data och ett inställningsobjekt. Om du använder merge: true innebär det att du bara uppdaterar det värde-nyckelpar som lämnats in i stället för att ersätta hela dokumentet med det du lämnade in.

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

När du nu klickar på ikonen för kontroll i din vy uppdateras din databas och försvinner eftersom attributet nu är inställt på true.

D står för Delete

För den sista åtgärden ska vi ställa in allt på samma sätt som för uppdateringsprocessen – men i stället för att uppdatera posten ska vi ta bort den.

Sätt upp en annan datacell med en klickhändelsehanterare som anropar en deleteOrder()-funktion. Vi kommer att skicka in instansen av order data i slingan när ikonen ”delete_forever” klickas. Du kommer att behöva detta för dokumentets id. Se koden nedan för exempel:

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

I din order-list.component.ts skapar du den tillhörande funktionen som anropar en deleteCoffeeOrder()-funktion i din orders.service.ts .

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

I din orders.service.ts-fil skapar du deleteCoffeeOrder()-funktionen och använder data-injiceringen för att ta reda på vad dokument-id är. Precis som vid uppdatering måste du känna till både samlingsnamnet och dokument-ID:t för att korrekt identifiera vilken post du vill ta bort. Använd .delete() för att tala om för Firestore att du vill ta bort posten.

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

Nu när du klickar på ikonen ”delete_forever” kommer din Angular-app att starta och tala om för Firestore att ta bort den specifika posten. Din post försvinner från vyn när det angivna dokumentet tas bort.

Slutligt program

Du hittar Github-arkivet för hela arbetsprojektet här. Du måste skapa din egen Firebase-databas och ansluta den själv genom att uppdatera konfigurationsfilerna. Koden i sig är inte perfekt men jag har hållit den mycket minimal så att du kan se hur Firestore fungerar i en Angular-app utan att behöva gå igenom en djungel av kod.

Jag har försökt att komprimera handledningen så mycket som möjligt utan att missa några detaljer. Det var en svår blandning men som du kan se ovan är mycket av det mest Angular-koden och inte mycket Firestore. Firebase Firestore kan göra mycket mer komplexa förfrågningar men för demonstrationsändamål har jag hållit det enkelt. I teorin kan du, om din datastruktur förblir densamma, byta ut Firestore och sätta in olika uppsättningar API-anslutningar med minimal refaktorisering som krävs.

Slutord

Personligen gillar jag Firebase på grund av hur enkelt det är att snabbt skapa produktionsklara projekt utan att behöva skapa en hel backend och server för att stödja det. Kostnadsmässigt är det inte så illa och testprojekt är gratis att starta upp (och de tvingar dig inte att ange dina kreditkortsuppgifter förrän du är redo att byta plan). På det hela taget tog det mer tid att skapa formuläret och gränssnittet runt appen än själva uppkopplingen av applikationen till Firestore. Jag har inte lagt till formulärvalidering i den här handledningen på grund av längden, men kommer att skapa ett annat inlägg om det i framtiden.

Lämna en kommentar