Moja pierwsza styczność z biblioteką AngularFire raczej była dość przykra – często zmieniające się API z kolejnymi wersjami czy tutoriale niskiej jakości. To wszystko skłoniło mnie do napisania tego artykułu, w którym chciałbym Ci przedstawić, jak wykonać logowanie za pomocą maila oraz jak konsumować dane z Firebase. W części pierwszej tutoriala, skupimy się na logowaniu, rejestracji oraz ochronie ścieżek dostępu poprzez Guard CanActivate. Przykład jest wykonany dla Angulara w wersji 6.0.0, AngularFire 5.0.0-rc.9 oraz Firebase 5.0.2.
Możesz również od razu spojrzeć na demo:
https://stackblitz.com/edit/angularfire-v6-qyrrf8
Firebase
Firebase to tzw. BaaS (Backend as a Service ), który umożliwia min. przechowywanie danych w formacie JSON oraz plików binarnych (np. jpg, mp4). Firebase dostarcza nam real-time database, gdzie komunikacja z serwerem jest oparta o Websockets, dzięki czemu po aktualizacji danych, klient automatycznie dostaje najświeższe dane. Obsługa jest banalnie prosta, w podstawowym zakresie można niemalże wszystko wyklikać z panelu użytkownika! Super rozwiązanie zwłaszcza dla osób, które nie mają wiedzy backendowej, a chcą postawić w szybkim czasie serwer z danymi.
Tyle na temat Firebase, jeśli chcesz lepiej poznać to narzędzie, musisz to zrobić we własnym zakresie. Natomiast w tym artykule, skupię na komunikacji z Firebase za pomocą biblioteki AngularFire.
Tworzymy backend!
Zanim rozpoczniemy komunikację z serwerem, musimy oczywiście sobie go wcześniej przygotować. Rozpoczynamy od odwiedzenia strony www.firebase.com, założenia konta, a następnie po zalogowaniu klikamy przycisk “OTWÓRZ KONSOLĘ” w prawym górnym rogu:
Następnie w kolejnym widoku, klikamy “UTWÓRZ PROJEKT”:
W okienku podajemy nazwę projektu, akceptujemy regulamin i klikamy “UTWÓRZ PROJEKT” i czekamy cierpliwie na powstanie projektu.
Po utworzeniu projektu, w lewym menu przechodzimy do zakładki “AUTHENTICATION”, a następnie klikamy tab “METODA LOGOWANIA”:
Z listy klikamy na pierwszą pozycję – EMAIL / HASŁO i przestawiamy na “WŁĄCZ”:
Oczywiście możesz poeksperymentować z innymi metodami logowania. Zapisujemy ustawienia… i na ten moment to tyle! mamy już przygotowany serwer z możliwością przechowywania i rejestracji użytkowników, czyż to nie piękne?:)
AngularFire
Czas skomunikować się z Firebase. Z pomocą przychodzi nam dedykowana biblioteka, główny bohater artykułu – AngularFire, który jest wrapperem na blibliotekę firebase.js. Pozwoli nam się zsynchronizować z danymi w czasie rzeczywistym, oraz dostarcza bardzo dobre API do logowania i monitorowania uwierzytelniania użytkownika.
Rozpoczynamy od instalacji paczki:
1 |
npm install firebase angularfire2 --save |
Po instalacji, wracamy do panelu Firebase i przechodzimy do ustawień projektu, klikając na zębatkę obok “PROJECT OVERVIEW” w lewym menu:
Scrollujemy nieco w dół i klikamy “Dodaj Firebase do swojej aplikacji internetowej”. Pojawia się okienko z danymi projektu:
Kopiujemy sam obiekt przypisany do “var config”. Reszta nas nie interesuje.
Podpięcie się pod Firebase
Wracamy do aplikacji angularowej i wklejamy plik konfiguracyjny do plików environment.ts oraz environment.prod.ts w katalogu src/environments. Jest to całkiem dobre miejsce na trzymanie takich globalnych ustawień. Obiekt możemy przypisać np. do pola “firebaseConfig”:
Następnie przechodzimy do app.module.ts i importujemy następujące moduły (AngularFireAuthModule i AngularFireModule):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
... import { AngularFireAuthModule } from 'angularfire2/auth'; import { AngularFireModule } from 'angularfire2'; @NgModule({ imports: [ AngularFireModule.initializeApp(environment.firebaseConfig), BrowserModule, AppRoutingModule, AngularFireAuthModule, ], ... }) |
Zwróć uwagę na wywołanie metody initializeApp z obiektem konfiguracyjnym, który zapisaliśmy w pliku environment.ts i environment.prod.ts. Teraz nasza aplikacja staje się świadoma backendu dostarczonego przez Firebase.
AuthService
Stworzymy teraz usługę, która pozwoli nam rejestrować i logować użytkowników:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import { Injectable } from '@angular/core'; import { AngularFireAuth } from 'angularfire2/auth'; import { User } from 'firebase'; import { Observable } from 'rxjs/index'; export interface Credentials { email: string; password: string; } @Injectable({providedIn: 'root'}) export class AuthService { readonly authState$: Observable<User | null> = this.fireAuth.authState; constructor(private fireAuth: AngularFireAuth) {} get user(): User | null { return this.fireAuth.auth.currentUser; } login({email, password}: Credentials) { return this.fireAuth.auth.signInWithEmailAndPassword(email, password); } register({email, password}: Credentials) { return this.fireAuth.auth.createUserWithEmailAndPassword(email, password); } logout() { return this.fireAuth.auth.signOut(); } } |
Właściwie wystarczy wstrzyknąć do konstruktora usługę AngularFireAuth, a reszta dosłownie dzieje się sama! Trzeba tylko wołać odpowiednie metody. Myślę, że nie ma sensu tłumaczyć co robią dane metody należące do fireAuth.auth, ich nazwy mówią wszystko. Możesz o nich więcej poczytać w dokumentacji. Warto natomiast zwrócić uwagę na linię:
1 |
readonly authState$: Observable<User | null> = this.fireAuth.authState; |
AuthState jest strumieniem, który emituje zalogowanego użytkownika. Jeśli użytkownik zostaje wylogowany, strumień wyemituje null.
Obiekt użytkownika jest przechowuje również w polu currentUser:
1 |
this.fireAuth.auth.currentUser; |
Także jak na tacy mamy wszystko co trzeba.
Persistence.LOCAL, SESSION, NONE
Warto wiedzieć, że możemy manipulować stanem uwierzytelnienia w różnych scenariuszach, takich jak np. zamknięcie karty i powrót (wylogować użytkownika automatycznie czy może nie?). Otrzymujemy możliwość zmiany właściwości PERSISTENCE, w poniższy sposób:
1 2 3 4 5 6 |
login({email, password}: Credentials) { const session = this.fireAuth.auth.Persistence.SESSION; return this.fireAuth.auth.setPersistence(session).then(() => { return this.fireAuth.auth.signInWithEmailAndPassword(email, password); }); } |
Możliwe 3 opcje do wyboru:
- LOCAL (DOMYŚLNE) – użytkownik nadal zostaje zalogowany po zamknięciu karty, trzeba jawnie użyć metody signOut aby wyczyścić stan zalogowania. Przydatne, jeśli po zamknięciu karty i ponownym powrocie, chcemy użytkownikowi pozwolić pozostać zalogowanym.
- SESSION – stan zalogowanego użytkownika jest aktywny wyłącznie dla aktualnej sesji i zostanie wyczyszczony w przypadku zamknięcia okna/karty. Przydatne np. w aplikacjach, które są publicznie dostępne na komputerach, z których korzysta wielu użytkowników (np. w bibliotece).
- NONE – stan zalogowania jest przetrzymywany w pamięci i zostanie wyczyszczony po odświeżeniu okna.
Wspomnę, że w Firebase 4.12.0 nastąpiła migracja przetrzymywania stanu użytkownika dla LOCAL PERSISTENCE z localStorage do IndexedDB, min. z takich powodów jak:
- użycie indexedDB umożliwi używanie Firebase Auth wraz z Service Workers i Web Workers
- indexedDB ma lepsze wsparcie w środowiskach takich jak np. Ionic/Cordova, lub aplikacje Chrome
Podgląd zarejestrowanych użytkowników
Po rejestracji, możemy zobaczyć założone konta w zakładce AUTHENTICATION:
Można również ręcznie dodać użytkownika, klikając “Dodaj użytkownika”.
AuthGuard
Obecnie w naszej aplikacji, gdy użytkownik odświeży stronę, to otrzymuje użytkownika z wartością null, gdyż currentUser będzie zaraz po odświeżeniu przechowywał wartość null, mimo, że stan uwierzytelnienia został zachowany.
Użytkownik może również przejść od razu na daną ścieżkę dowolnego widoku z pominięciem etapu logowania, oczywiście jeśli zna ścieżkę(za wiele mu to i tak nie da, bo bez zalogowania dane i tak nie zostanę pobrane).
Oba problemy może rozwiązać za pomocą guarda CanActivate. Podczas przejścia na widok inny niż login, sprawdzimy czy authState przechowuje stan użytkownika, jeśli tak, to go przepuścimy, natomiast jeśli wyemituje null, no to cofniemy go do widoku loginu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; import { Observable } from 'rxjs'; import { AuthService } from './auth.service'; import { map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor( private authService: AuthService, private router: Router, ) {} canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { return this.authService.authState$.pipe(map(state => { if (state !== null) { return true; } this.router.navigate(['/login']); return false; } ) ); } } |
Guard canActivate zwraca strumień z true/false, w zależności czy authState$ wyemitował stan użytkownika (emisja true) czy wyemitował null (emisja false).
Strumień jest już obsługiwany przez mechanizm routingu w Angularze, także nie musimy się martwić o manualną subskrypcję w żadnym miejscu. Można oczywiście użytkownika przekierować np. na widok z informacją, że nie jest autoryzowany i prosimy o zalogowanie. Decyzja należy do Ciebie, pokazałem najprostszy przykład.
Teraz naszego guarda musimy nałożyć na odpowiednie ścieżki, np. na cały dashboard, który będzie sprawdzał również wszystkie ścieżki zawarte w “children”:
1 2 3 4 5 6 7 8 9 10 |
const routes: Routes = [ { path: '', redirectTo: '/login', pathMatch: 'full'}, { path: 'login', component: LoginComponent }, { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard], children: [...] }, ]; |
Teraz wszystko jest po bożemu, użytkownik jest sprawdzany na etapie przechodzenia między ścieżkami, oraz nie ma problemu, że użytkownik jest równy null na widoku po odświeżeniu aplikacji.
DEMO:
https://angularfire-v6-qyrrf8.stackblitz.io
PEŁNY KOD:
https://stackblitz.com/edit/angularfire-v6-qyrrf8
W kolejnej części artykułu, omówię jak konsumować dane – pobierać, dodawać, edytować, usuwać. Wykonamy jakiś fancy przykład, na pewno coś ciekawszego niż TodoList :).
Bardzo polecam Firebase początkującym developerom, którzy chcą stworzyć realny projekt do portfolio 🙂
Ja naleze do grupy poczatkujacych developerow, ktorzy kojarza Firebase, pracuja z Angularem i maja w planach zrobic cos w wolnym czasie z tymi technologiami, wiec czekam na inspiracje w kolejnych artykulach!
Ps: bez polskich znakow, bo nadaje z zagranicy
super! mam nadzieję, że artykuły się przydadzą 🙂
Wincyj kurcze wincyj 🙂
Super artykuł. Kiedy będzie ciąg dalszy? Nie mogę się doczekać. Gratuluję świetnej pracy.
Jak to się ma do większych projektów? Opłaty nie przytłaczają?
hej Bartek
przepraszam, ze dopiero odpisuje, przeoczyłem Twój komentarz. Nie wiem jak z opłatami, nie używałem FB w płatnej wersji
Mam takie prolem, że po odświeżeniu apki najpierw pokazuje mi się widok loginu i dopiero po sekundzie widok dla zalogowanych. Ktoś wie co może być przyczyną?
Bardzo dobry i łatwo przyswajalny tutorial, trafiłem na niego zupełnie przypadkiem. A tak się ciekawie składa, że nasz wykładowca z kierunku postanowił go sobie “pożyczyć” do prowadzenia zajęć 🙂