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.html
i 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.
Dodaj nazwę projektu, zaakceptuj warunki i kliknij na create project.
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.
Wybierz start w trybie testowym dla reguł bezpieczeństwa. Możesz je później zmodyfikować.
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:
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 or
der-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.