How to CRUD in Angular + Firebase Firestore

CRUD – Create, Read, Update, Delete – cztery święte Graale akcji bazodanowych. Są to jedyne rzeczy, których kiedykolwiek będziesz potrzebował, aby zrobić cokolwiek znaczącego z bazą danych. Jasne, możesz zwiększyć złożoność zapytań, ale na koniec dnia, wszystko sprowadza się do tych czterech działań.

Firebase jest systemem Google opartym na chmurze, który jest wyposażony w haki API, przestrzeń do przechowywania plików, system autoryzacji i możliwości hostingowe. Jest to bardzo niedoceniany system, który powinien być wykorzystywany bardziej do prototypowania i szybkiego tworzenia aplikacji.

Jeśli chcesz uruchomić progresywną aplikację internetową, ale nie masz doświadczenia w tworzeniu serwerów, tworzeniu API i radzeniu sobie z bazami danych, to Firebase jest fantastyczną opcją dla programistów front-end, którzy mogą czuć się odizolowani i pogrążeni w ogromie informacji, które muszą przetworzyć, aby ich aplikacja zaczęła działać.

Or jeśli jesteś po prostu krótki na czas, Firebase może wyciąć swoje godziny rozwoju prawie w połowie, dzięki czemu można skupić się na doświadczeniach użytkownika i wdrożenie tych przepływów UI. Jest również wystarczająco elastyczny, aby zmigrować swoją aplikację front-end i użyć innej bazy danych w razie potrzeby.

Tutaj znajduje się szybki przewodnik, jak zaimplementować działania CRUD z Angular i Firebase.

Jest to aplikacja do zamawiania kawy, w której można dodawać zamówienia (tworzenie), wyświetlać listę zamówień z bazy danych (odczyt), oznaczać zamówienia jako zakończone (aktualizacja) i usuwać zamówienia (usuwanie).

Celem tego poradnika jest pomoc w rozpoczęciu pracy z Firebase Firestore i zobaczenie, jak łatwo jest połączyć się z usługą należącą do Google i rozpocząć pracę z nią. Nie jest to reklama dla Google (nie dostaję od nich za to żadnych gratyfikacji), a jedynie ilustracja tego, jak Angular gra z bazą danych.

Aplikacja, którą zamierzamy stworzyć, nie jest idealna. Brakuje w niej takich rzeczy jak walidacja danych i miliona możliwych funkcji, które też mogę dodać. Ale nie o to chodzi. Chodzi o to, aby jak najszybciej skonfigurować Angulara i uruchomić go z żywą bazą danych.

Więc, dość wstępu – oto kod do przejścia.

Wstępna konfiguracja

Skonfiguruj swoją aplikację Angular za pomocą Angular CLI. Jeśli nie masz jeszcze Angular CLI, możesz dowiedzieć się więcej na ten temat tutaj.

W skrócie, po prostu uruchom te polecenia w swoim terminalu w katalogu, w którym chcesz, aby Twoja aplikacja Angular siedziała. Oto polecenia i to, co robią.

npm install -g @angular/cli

Instaluje Angular CLI na twoim terminalu, jeśli jeszcze go nie masz.

ng new name-of-app-here

Tworzy nowy projekt Angular używając najnowszej dostępnej wersji Angular.

cd name-of-app-here

Gdy utworzysz nowy projekt przez Angular CLI, utworzy on dla ciebie nowy folder projektu. cd przeniesie Cię do tego folderu przez terminal.

ng serve

To jest uruchomienie Twojego projektu Angular.

Ustawianie Angulara

Dla tej aplikacji Angularowej stworzymy w sumie 3 nowe części – orders, order-list i shared/ordersService. Pierwsze dwa to komponenty, które będą zawierały interfejs aplikacji, oraz usługa shared/orders, która będzie utrzymywała wszystkie wywołania API Firebase razem.

Aby utworzyć potrzebne pliki, uruchom następujące polecenia:

ng g c orders

g oznacza generate, a c oznacza component. Ostatnia część polecenia to nazwa Twojego pliku, który w powyższym przypadku nazywa się orders. Możesz również użyć ng generate component orders, aby osiągnąć ten sam efekt.

Utwórz kolejny komponent dla order-list używając następującego polecenia.

ng g c order-list

I wreszcie, dla usługi, użyj s zamiast c jak poniżej:

ng g s shared/orders

To utworzy plik orders.service.ts w folderze o nazwie shared. Pamiętaj, aby dodać orders.service.ts do app.module.ts, ponieważ nie jest to wykonywane automatycznie, jak w przypadku komponentów. Możesz to zrobić poprzez import i dodanie go do listy providers w następujący sposób:

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

Kodowanie CSS

W tym tutorialu, będziemy używać Materialize CSS aby nasza końcowa aplikacja wyglądała lepiej niż domyślne css. Nie jest to konieczne i możesz użyć dowolnego frameworka CSS lub swojego własnego CSS jeśli chcesz. Możesz sprawdzić szczegóły Materialize CSS tutaj.

Będziemy również używać materialnych ikon Googla jako przycisków do oznaczania zamówienia kawy jako zrealizowanego lub usuwania zamówienia.

Jednym ze sposobów na zaimplementowanie tego jest posiadanie poniższego kodu tuż nad znacznikiem </head> w swoim pliku index.html znajdującym się w folderze 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" />

Kod początkowy widoku Angular

W app.component.html usuń cały kod startowy. Będziemy do tego używać własnej zawartości.

Zaczepimy się do komponentu, który właśnie stworzyliśmy, i wyświetlimy je na ekranie za pomocą selektorów app-orders i app-order-list. Aby to zrobić, napiszemy poniższy kod:

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

Klasy container, row, col i s6 są częścią systemu siatki Materialize CSS. Wszystkie klasy, które zobaczysz w pozostałej części tego tutoriala pochodzą z Materialize CSS, chyba że wspomniano inaczej.

Ustawianie formularza

Aby ustawić formularze, zaimportuj ReactiveFormsModule w app.module.ts i pamiętaj o zaimportowaniu go w tablicy imports.

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

Wewnątrz orders.service.ts zaimportuj FormControl i FormGroup z @angular/forms i utwórz nowy formularz poza constructor, gdzie możesz ustawić właściwości formularza jak poniżej:

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

Użyjemy tych wartości do przechowywania w Firebase trochę później w tym samouczku.

Używanie formularza wewnątrz komponentu

import klasę OrdersService do swojego komponentu, abyś mógł użyć obiektu wewnątrz swojego komponentu, a następnie utworzyć obiekt tej klasy wewnątrz constructor.

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

Wewnątrz swojego orders.component.html użyj dyrektywy formGroup, aby odwołać się do obiektu formularza utworzonego w OrdersService. Każdy formControlName odwołuje się do nazw, których użyliśmy w dyrektywie formGroup utworzonej wewnątrz klasy OrdersService. Pozwoli to powiązanemu kontrolerowi na użycie zmiennych wpisanych do formularza. Zobacz poniższy kod:

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

W orders.component.ts zamierzamy ustawić tablicę do zapętlenia w naszym orders.component.html. Teoretycznie, mógłbyś również skonfigurować to wewnątrz swojej bazy danych Firestore, wykonać wywołanie do kolekcji, a następnie użyć jej. Ale dla celów długości, ustawimy to jako lokalną tablicę. Wewnątrz klasy OrdersComponent ustaw następującą tablicę:

coffees = ;

W znacznikach orders.component.html i <form> zapętl ją, używając *ngFor z uchwytem akcji (click), aby dodać nowe kawy do zamówienia. Zamierzamy wyświetlić listę zamówień tuż poniżej z możliwością usuwania poszczególnych kaw w następujący sposób:

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

Wewnątrz orders.component tworzysz pustą tablicę do przechowywania zamówienia kaw, używasz funkcji addCoffee() do dodawania nowych kaw oraz removeCoffee() do usuwania napojów z listy zamówień.

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

Obsługa składania formularzy

Dodaj wejście Submit Order wewnątrz i na dole znaczników <form> i dodaj onSubmit() do obsługi kliknięcia jak poniżej:

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

Stwórz pustą onSubmit funkcję w swoim orders.component na okres przejściowy. Wkrótce dodamy do niej obsługę zdarzeń. W tym momencie twój formularz powinien być gotowy do podłączenia się do bazy danych Firebase.

Ustawianie komponentu listingowego

Teraz potrzebujemy tylko miejsca do wyświetlania naszych zamówień na kawę z bazy danych. Na razie zajmiemy się ustawieniem rusztowania html.

Przejdź do swojego order-list.component.htmli utwórz tabelę z 3 nagłówkami i 5 komórkami danych. Pierwsze 3 komórki danych będą zawierać wartości pobrane z bazy danych, a ostatnie 2 będą zawierać dodatkową funkcjonalność, która pozwoli Ci oznaczyć zamówienie jako kompletne lub usunąć zamówienie.

Ustawianie Firebase

Przejdź do swojej konsoli Firebase i dodaj nowy projekt.

Kliknij na 'Add Project’, aby utworzyć nowy projekt.

Dodaj nazwę projektu, zaakceptuj warunki i kliknij na create project.

Nadaj swojemu projektowi nazwę i zaakceptuj warunki kontrolera-kontrolera, aby utworzyć projekt.

Gdy Twój projekt Firebase został utworzony, zobaczysz coś takiego.

Utwórz bazę danych wybierając Database na panelu bocznym (znajdującym się pod Develop), a następnie kliknij na Create Database pod banerem Cloud Firestore.

Kliknij na 'Create database’

Wybierz start w trybie testowym dla reguł bezpieczeństwa. Możesz je później zmodyfikować.

Pamiętaj, aby zachować swoje poświadczenia na komputerze lokalnym tylko dla celów bezpieczeństwa.

Powstanie pusta baza danych Firestore, jak poniżej.

Podłączanie Firebase Firestore do Angular

Aby podłączyć aplikację Angular do Firebase, musisz zainstalować pakiety firebase i @angular/fire. To da ci dostęp do AngularFireModule i AngularFirestoreModule. Użyj następującego polecenia w swoim terminalu, aby je zainstalować.

npm i --save firebase @angular/fire

Wdrażanie złączy

Powróć do swojej konsoli internetowej Firebase i pobierz szczegóły konfiguracji, aby użyć ich w swojej aplikacji Angular. Znajduje się to na stronie Przegląd projektu. Wygląda to mniej więcej tak:

Będziesz musiał użyć własnych danych uwierzytelniających. Powyższy config nie będzie działał dla Ciebie.

Kopiuj zaznaczoną część kodu, przejdź do swojego pliku environment.ts (znajdującego się wewnątrz folderu environments) i wklej szczegóły configu wewnątrz obiektu environment jako firebaseConfig. Zobacz przykład poniżej:

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

Importuj pakiety, które zainstalowałeś wcześniej i plik environment.ts do swojego app.module.ts.Będziesz musiał zainicjować AngularFireModule z firebaseConfig, dla którego właśnie wykonałeś konfigurację. Zobacz poniższy przykład:

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

Ustawianie Firestore w usłudze

Importuj AngularFirestore do swojego pliku orders.service.ts i zadeklaruj go w konstruktorze, aby twoja usługa o nim wiedziała.

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

Teraz wszystko jest ustawione i gotowe do rozpoczęcia części CRUD tego samouczka Angular + Firebase Firestore.

C jest dla Create

Aby utworzyć nowy rekord w twojej zupełnie nowej bazie danych Firestore, będziesz musiał zadzwonić do .add() . Zrobimy to wewnątrz pliku orders.service.ts.

Aby to zrobić, musisz określić nazwę collection oraz dane, które chcesz przesłać do bazy danych. W naszym przypadku jest to coffeeOrders .

Przykładowy kod poniżej używa obietnicy, aby zwrócić wywołanie Firebase, a następnie pozwala zdecydować, co chcesz zrobić po zakończeniu.

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

Aby wywołać tę funkcję w swoim komponencie, przejdź do orders.component.ts i wewnątrz onSubmit() funkcji i action handler, wykonaj wywołanie createCoffeeOrder() przez ordersService.

Poniższy przykład wykonuje pewne przetwarzanie danych, mapując tablicę coffeeOrder do wartości formularza coffeeOrder. Utworzyłem również zmienną data dla celów destrukcji (wraz z brakiem konieczności przekazywania masywnie długiej nazwy do 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*/
});
}
...

I viola! Teraz utworzyłeś rekord ze swojej aplikacji Angular na localhost do bazy danych Firestore.

R is for Read

Aby wyświetlić dane o zamówieniach kawy, będziesz potrzebował funkcji read w swoim orders.service.ts. Poniższy przykład kodu da ci wszystkie wartości przechowywane w twojej kolekcji coffeeOrders.

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

Aby użyć tej funkcji, będziesz musiał wywołać ją z twojego orders-list.component.ts . Możesz to zrobić, importując OrdersService do pliku i inicjalizując go w constructor.

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

Stwórz funkcję zawierającą twoje wywołanie i zainicjalizuj ją na ngOnInit(), aby wywołać ją, gdy widok zostanie załadowany po raz pierwszy. Utwórz zmienną coffeeOrders, aby odwzorować zwrócone wyniki z twojej bazy danych poprzez subscribe(). Użyjemy tego do iteracji i wyświetlenia w order-list.component.html

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

Aby użyć coffeeOrders w twoim order-list.component.html , użyj *ngFor do pętli przez zwróconą tablicę. Musisz również zrobić trochę incepcji i wykonać kolejną pętlę dla części coffeeOrder, aby uzyskać listę kaw dla każdego klienta. Istnieją bardziej wydajne sposoby, aby to zrobić, ale dla tego samouczka, zobacz przykładowy kod poniżej:

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

I tam masz to. Podłączyłeś teraz możliwości odczytu do swojej aplikacji Angular.

U is for Update

Powiedzmy, że chcemy być w stanie oznaczyć zamówienie kawy jako kompletne w bazie danych i zrobić coś z pozycją linii na podstawie tej zmiany. Ponieważ snapshot() śledzi wszelkie zmiany, które zachodzą, nie musisz wykonywać żadnego śledzenia ani odpytywania bazy danych.

Utworzymy kolejną komórkę danych w naszej tabeli z ikoną 'check’ z Google Materialize Icons i podepniemy ją do zdarzenia (click), które wywoła funkcję markCompleted() w twoim order-list.component.ts. Zamierzamy również przekazać konkretną kolejność dla tej pętli.

Zamierzamy ustawić atrybut z wartością completed do komórki danych, aby mogła ona dynamicznie określić, czy chcemy mieć ikonę 'check’ na wyświetlaczu. Pierwotnie ustawiliśmy tę wartość jako false, gdy po raz pierwszy utworzyliśmy formularz w orders.service.ts .

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

Inside order-list.component.ts , utwórz funkcję markCompleted(), która używa wstrzykniętego data do przekazania do funkcji o nazwie updateCoffeeOrder() w orders.service.ts.

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

Inside orders.service.ts utwórz funkcję obsługi. Funkcja ta będzie łączyć się i wywoływać bazę danych Firestore na podstawie wybranej kolekcji i id dokumentu. Wiemy już, że nasza kolekcja nazywa się coffeeOrders i możemy znaleźć id dokumentu na podstawie parametrów przekazanych w wywołaniu funkcji komponentu.

.set() ustawi konkretny rekord z danymi, które zostały przekazane. .set() przyjmuje dwa parametry – Twoje dane oraz obiekt ustawień. Jeśli użyjesz merge: true, oznacza to, że aktualizujesz tylko przekazaną parę wartość-klucz, a nie zastępujesz całego dokumentu tym, co przekazałeś.

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

Teraz, gdy klikniesz ikonę „sprawdź” w swoim widoku, zaktualizuje ona twoją bazę danych i zniknie, ponieważ twój atrybut jest teraz ustawiony na true.

D is for Delete

Dla ostatniej akcji, zamierzamy ustawić wszystko w podobny sposób jak proces aktualizacji – ale zamiast aktualizować rekord, zamierzamy go usunąć.

Ustaw kolejną komórkę danych z obsługą zdarzenia kliknięcia, która wywołuje funkcję deleteOrder(). Przekażemy instancję danych order w pętli, gdy ikona 'delete_forever’ zostanie kliknięta. Będziesz tego potrzebował dla id dokumentu. Zobacz poniższy kod dla przykładu:

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

Wewnątrz twojego order-list.component.ts utwórz powiązaną funkcję, która wywołuje funkcję deleteCoffeeOrder() wewnątrz twojego orders.service.ts .

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

Wewnątrz twojego orders.service.ts pliku, utwórz funkcję deleteCoffeeOrder() i użyj data wstrzykniętego, aby dowiedzieć się, jaki jest id dokumentu. Podobnie jak w przypadku update, musisz znać zarówno nazwę kolekcji, jak i id dokumentu, aby poprawnie zidentyfikować rekord, który chcesz usunąć. Użyj .delete(), aby powiedzieć Firestore, że chcesz usunąć rekord.

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

Teraz, gdy klikniesz na ikonę „delete_forever”, Twoja aplikacja Angular wystrzeli i powie Firestore, aby usunął określony rekord. Twój rekord zniknie z widoku, gdy określony dokument zostanie usunięty.

Aplikacja końcowa

Możesz znaleźć repozytorium Github dla całego działającego projektu tutaj. Będziesz musiał stworzyć własną bazę danych Firebase i podłączyć ją samodzielnie poprzez aktualizację plików konfiguracyjnych. Sam kod nie jest idealny, ale utrzymałem go na bardzo minimalnym poziomie, abyś mógł zobaczyć jak Firestore działa w aplikacji Angular bez konieczności przebrnięcia przez dżunglę kodu.

Starałem się skondensować tutorial tak bardzo jak to możliwe bez pomijania żadnych szczegółów. To była trudna mieszanka, ale jak widać powyżej, wiele z tego to głównie kod Angulara i niewiele Firestore. Firebase Firestore może wykonywać o wiele bardziej złożone zapytania, ale dla celów demonstracyjnych utrzymałem go w prostocie. W teorii, jeśli twoja struktura danych pozostaje taka sama, możesz wymienić Firestore i umieścić inny zestaw połączeń API z minimalnym wymaganym refaktoringiem.

Słowa końcowe

Osobiście lubię Firebase ze względu na to, jak łatwo jest szybko tworzyć projekty gotowe do produkcji bez potrzeby tworzenia całego backendu i serwera do jego obsługi. Koszt nie jest zły, a projekty testowe są darmowe do uruchomienia (i nie każą ci wprowadzać danych karty kredytowej, dopóki nie będziesz gotowy do zmiany planów). Ogólnie rzecz biorąc, stworzenie formularza i interfejsu otaczającego aplikację zajęło więcej czasu niż sam akt podłączenia aplikacji do Firestore. Nie dodałem walidacji formularzy do tego tutoriala ze względu na długość, ale stworzę o tym kolejny post w przyszłości.

Dodaj komentarz