Kurs Angular · Moduł 4: Routing
Strażnicy bram - guards trasy
W tej lekcji7
Zrób jeden eksperyment zanim przeczytasz choćby zdanie teorii. Wyloguj się ze swojej aplikacji, a potem wpisz w pasku przeglądarki adres /admin i naciśnij enter. Panel dowodzenia otworzy się bez mrugnięcia okiem. Router zrobił dokładnie to, czego go nauczyłeś: wziął napis z paska, znalazł pasujący wpis w tablicy tras i wstawił komponent do outletu. Pytanie "kto ty jesteś i czy wolno ci tędy przejść" nie padło ani razu, bo w całej tej maszynie nie ma jeszcze miejsca, w którym mogłoby paść.
Nie licz na to, że wystarczy ukryć link w nawigacji. Ukryty link znika z widoku, ale adres zostaje, a adres da się przepisać z pamięci, wyciągnąć z zakładek albo dostać od kogoś w wiadomości. Ochrona, którą omija się wpisaniem sześciu znaków z klawiatury, nie jest ochroną.
W Akademii nikt nie zostawia bramy bez obsady. Mur ma jedno przejście, a w przejściu stoi strażnik: zatrzymuje przybysza, ogląda pieczęć na jego zwoju i podejmuje jedną z dwóch decyzji - przepuścić albo zawrócić. Nie renderuje widoku, nie pobiera zapasów, nie zdobi dziedzińca. Odpowiada tak albo nie. W Angularze taki strażnik nazywa się guard, a Ty postawisz w tej lekcji trzech: przy bramie głównej tego, który sprawdza, czy przybysz jest w ogóle zalogowany, przy bramie wewnętrznej tego, który patrzy na rangę, a przy furcie wyjściowej tego, który nie wypuszcza nikogo z niedokończoną robotą.
Strażnik to zwykła funkcja
Guard to funkcja, której nigdy nie wołasz sam. Wołanie należy do routera i odbywa się w bardzo konkretnej chwili: adres jest już dopasowany do wpisu w tablicy tras, ale komponent jeszcze nie powstał i nic nie trafiło na ekran. Router pyta strażnika o zdanie i patrzy na to, co funkcja zwróciła. Prawda oznacza "przepuść" i podróż toczy się dalej. Fałsz oznacza "zawróć": nawigacja zostaje odwołana, komponent nie powstaje, a adres w pasku wraca do poprzedniego.
Żeby TypeScript wiedział, jakiego kształtu funkcji się tu spodziewać, pakiet @angular/router eksportuje gotowy alias typu o nazwie CanActivateFn. Alias typu to nadana z góry nazwa dla pewnego kształtu - w tym wypadku dla funkcji o dwóch parametrach, zwracającej decyzję. Kiedy podpiszesz nim swoją stałą po dwukropku, nie musisz pisać w środku ani jednej adnotacji: typy obu parametrów TypeScript dopowie sam, a typ wyniku sam sprawdzi.
Zapamiętaj tę nazwę co do litery, bo dwie inne, które brzmią równie sensownie, po prostu nie istnieją. Pakiet @angular/router nie eksportuje niczego o nazwie GuardFn ani ActivateGuard - oba importy sprawdziłem kompilatorem TypeScriptu na Angularze 19 i oba kończą się tym samym błędem o braku takiego eksportu. Istnieje za to nazwa CanActivate bez końcówki i to jest prawdziwa pułapka, bo należy do starego wariantu: klasy z dekoratorem, która implementowała interfejs o tej nazwie i trafiała do trasy jako token wstrzykiwania. Strażnicy funkcyjni pojawili się w Angularze 14, wariant klasowy jest w dokumentacji oznaczony jako przestarzały i to funkcji będziemy się tu trzymać. Adnotujesz więc stałą typem CanActivateFn, nigdy CanActivate.
Zacznijmy od strażnika, który przepuszcza wszystkich - żeby najpierw zobaczyć samą konstrukcję, bez decyzji w środku. Plik nazwiemy zgodnie ze zwyczajem Angulara, po roli i po typie.
// guards/auth.guard.ts
import { CanActivateFn } from '@angular/router';
export const authGuard: CanActivateFn = () => {
return true;
};Ten strażnik jeszcze niczego nie pilnuje, bo zwraca samą prawdę, i co ważniejsze, nie zostanie na razie ani razu wywołany: plik leży w projekcie, a router nic o nim nie wie. Zwróć uwagę na to, czego tu nie ma. Nie ma klasy, nie ma dekoratora, nie ma słowa new - to zwykła stała ze strzałkową funkcją, dokładnie taka, jakie pisałeś w JavaScripcie. Nie ma też żadnej adnotacji przy parametrach ani przy wyniku, bo całą tę wiedzę wniósł CanActivateFn stojący po dwukropku. Puste nawiasy przy strzałce są w pełni poprawne: skoro z argumentów nie korzystasz, nie musisz ich wypisywać, a TypeScript pozwala podstawić funkcję o mniejszej liczbie parametrów tam, gdzie oczekiwana jest funkcja o większej. Za chwilę po nie sięgniemy i wtedy się pojawią.
Postawić strażnika przy bramie
Sam strażnik nikogo nie zatrzyma, dopóki nie każesz mu stanąć w konkretnym przejściu. Miejsce wskazuje się we wpisie trasy, polem canActivate. Pole przyjmuje tablicę, i to nie z uprzejmości: przy jednej bramie może stać kilku strażników, każdy od czego innego. Router wywoła ich w kolejności zapisu i przepuści przybysza dopiero wtedy, gdy wszyscy powiedzą tak; pierwsza odmowa kończy sprawę, a pozostali nie są już pytani.
Przyjrzyj się kolejności zapisu w tym jednym wierszu, bo będziesz ją odtwarzał token po tokenie: najpierw klamra otwierająca razem ze ścieżką, potem komponent, potem tablica ze strażnikami, a na samym końcu klamra zamykająca wpis. Importy też są istotne - stałą ze strażnikiem sprowadzasz tak samo jak komponent, bo to po prostu kolejny eksport z Twojego projektu.
// app.routes.ts
import { Routes } from '@angular/router';
import { AdminComponent } from './admin/admin.component';
import { authGuard } from './guards/auth.guard';
export const routes: Routes = [
{ path: 'admin', component: AdminComponent, canActivate: [authGuard] }
];Od tej chwili każde wejście na /admin przechodzi przez strażnika - który wciąż przepuszcza wszystkich, bo w środku ma samo return true, ale już bierze udział w cyklu życia trasy. Najważniejszy szczegół siedzi w tablicy: przy nazwie authGuard nie ma nawiasów. Podajesz samą funkcję, a nie jej wynik. Gdybyś napisał tam [authGuard()], próbowałbyś wywołać strażnika natychmiast, w chwili wczytywania pliku z trasami, i do tablicy trafiłby jego wynik zamiast jego samego. Akurat tę pomyłkę TypeScript wyłapie za Ciebie - sprawdziłem kompilatorem: odpowiada, że funkcja oczekiwała dwóch argumentów, a dostała zero. Zwróć też uwagę, co zostało nietknięte: AdminComponent. Komponent nie wie, że ktokolwiek go pilnuje, nie ma w nim ani linijki sprawdzającej uprawnienia i nie powinno jej być - decyzja zapada przed nim, na poziomie trasy.
Zapytać o pieczęć
Strażnik, który mówi tak każdemu, jest tylko ozdobą. Prawdziwy musi kogoś zapytać o stan rzeczy, a wiedza o tym, kto jest zalogowany, nie leży ani w routerze, ani w komponencie. Umówmy się, że mamy w projekcie własny serwis AuthService z dwiema metodami: isLoggedIn() odpowiada prawdą albo fałszem na pytanie o zalogowanie, a hasRole() przyjmie nazwę rangi i powie, czy przybysz ją ma. Wnętrze takiego serwisu zbudujesz w module o gildiach rzemieślniczych; tutaj traktuj go jak zamkniętą skrzynkę z dwoma pytaniami.
Po serwis sięgasz funkcją inject() z pakietu @angular/core, tą samą, którą w poprzedniej lekcji prosiłeś o zwój aktywnej trasy. Jest tu jednak warunek, o którym warto wiedzieć od razu: inject() wolno wywołać wyłącznie w tak zwanym kontekście wstrzykiwania, czyli w miejscu, w którym Angular ma pod ręką swój wstrzykiwacz. Ciało guarda takim miejscem jest, bo to router wywołuje tę funkcję i sam ten kontekst przygotowuje. Wywołanie musi być jednak synchroniczne, czyli od razu w ciele strażnika, a nie w środku funkcji odpalanej później - dlatego inject() piszemy w pierwszych liniach, zanim zacznie się jakakolwiek logika.
// guards/auth.guard.ts
import { inject } from '@angular/core';
import { CanActivateFn } from '@angular/router';
import { AuthService } from '../services/auth.service';
export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService);
return auth.isLoggedIn();
};Brama zaczęła wreszcie filtrować: zalogowany przechodzi, wylogowany zostaje zawrócony i AdminComponent w ogóle nie powstaje. Co się przy tym nie zmieniło - i to jest tu najciekawsze - wpis trasy wygląda dokładnie tak samo jak przed chwilą, typ strażnika też jest ten sam, a nazwa stałej nadal brzmi authGuard, więc plik z trasami nie wymaga ani jednej poprawki. Zmieniło się wyłącznie wnętrze funkcji: zamiast stałej prawdy zwracasz teraz wynik pytania zadanego serwisowi.
Zostaje jednak rzecz, która zepsuje wrażenie użytkownikowi. Gdy isLoggedIn() zwróci fałsz, gość zobaczy... nic. Ekran, na którym stał, zostaje na miejscu, adres wraca do poprzedniego, żaden komunikat się nie pojawia. Z jego punktu widzenia kliknięcie po prostu nie zadziałało i nie sposób odróżnić odmowy od zepsutego przycisku.
Zawrócić, ale ze wskazaniem drogi
Strażnik przy prawdziwej bramie nie milczy - odsyła przybysza do kancelarii po pieczęć. Guard umie zrobić dokładnie to samo, bo wolno mu zwrócić coś więcej niż wartość logiczną. Może oddać obiekt typu UrlTree, czyli gotowy adres rozłożony na części pierwsze: segmenty ścieżki, parametry zapytania, fragment. Dla routera taki zwrot znaczy "nie wpuszczaj go tutaj, tylko przenieś tam" i jest to jedna operacja, nie dwie. Wolno mu też zwrócić strumień albo obietnicę takiej odpowiedzi, gdy decyzja wymaga zapytania serwera - wtedy router poczeka na wynik.
UrlTree budujesz metodą createUrlTree() serwisu Router, tego samego, którym w poprzedniej lekcji wydawałeś rozkaz wymarszu. Metoda przyjmuje te same argumenty co navigate(): tablicę segmentów oraz opcjonalny obiekt ustawień z polem queryParams. Różnica jest zasadnicza i warto ją mieć z tyłu głowy: navigate() wykonuje przejście od razu, a createUrlTree() tylko buduje opis adresu i oddaje go Tobie. Polecam Ci zwracanie UrlTree i odradzam wołanie navigate() w środku strażnika: wywołanie navigate() startuje drugą nawigację, kiedy pierwsza jeszcze się nie zakończyła, a dwie podróże biegnące jednocześnie potrafią zostawić w pasku adres z tej przegranej.
Do adresu logowania dołożymy jeszcze dopisek: adres, na który gość próbował wejść, żeby po zalogowaniu wrócił tam, gdzie zaczął. Bierzemy go z drugiego parametru strażnika, więc czas wreszcie wypisać oba. Pierwszy parametr, tradycyjnie nazywany route, to migawka trasy docelowej - ten sam rodzaj obiektu, z którego czytałeś paramMap. Drugi, state, to migawka całego stanu routera, a w niej pole url z pełnym adresem, do którego zmierzała nawigacja.
// guards/auth.guard.ts
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
export const authGuard: CanActivateFn = (route, state) => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isLoggedIn()) {
return true;
}
// zawroc na logowanie i zapamietaj, dokad gosc zmierzal
return router.createUrlTree(['/login'], {
queryParams: { returnUrl: state.url }
});
};Uruchomiłem tego strażnika na prawdziwym routerze Angulara 19. Niezalogowany gość, który wpisze /admin, ląduje na adresie /login?returnUrl=%2Fadmin - ukośnik w dopisku jest zakodowany, bo router sam koduje znaki specjalne w parametrach zapytania, i to jest dokładnie ta wartość, którą ekran logowania odczyta przez queryParamMap. Zalogowany przechodzi na /admin bez żadnej zmiany w zachowaniu. Nie zmieniła się przy tym ani tablica tras, ani typ strażnika: CanActivateFn od początku dopuszczał trzy rodzaje odpowiedzi i dopiero teraz korzystamy z drugiego. Zwróć uwagę, że gałąź z return true stoi przed przekierowaniem i kończy funkcję - to typowa konstrukcja strażnika: najpierw wypuść przypadek dozwolony, potem zajmij się odmową.
Ranga wypisana we wpisie trasy
Zalogowany to jeszcze nie znaczy uprawniony. Do panelu dowodzenia wchodzi shogun, do zbrojowni kowal, a zwykły uczeń do żadnego z nich - i teraz mogłoby się wydawać, że potrzebujesz osobnego strażnika dla każdej rangi. Nie potrzebujesz. Wystarczy jeden, jeśli dostanie w rozkazie nazwę rangi, której ma pilnować.
Do przekazania takiego rozkazu wpis trasy ma pole data. To zwykły obiekt z Twoimi własnymi kluczami i wartościami, doklejony do konfiguracji trasy i widoczny potem w migawce. Nazwa jest jedna i nie ma dla niej zamienników: Route nie zna pola meta, nie zna pola config i nie zna pola params - parametry biorą się z adresu, a nie z konfiguracji, więc w pliku z trasami nie ma czego takiego wpisywać. Każdą z tych trzech nazw wpisałem do trasy i skompilowałem: wszystkie trzy kończą się tym samym komunikatem TypeScriptu, że taka właściwość nie istnieje w typie Route.
Postawmy więc przy bramie dwóch strażników naraz i dopiszmy im rozkaz. Kolejność w tablicy nie jest przypadkowa: najpierw sprawdzamy zalogowanie, dopiero potem rangę, bo pytanie o rangę kogoś anonimowego nie ma sensu.
// app.routes.ts, fragment tablicy tras
{
path: 'admin',
component: AdminComponent,
canActivate: [authGuard, roleGuard],
data: { role: 'shogun' }
}Wpis urósł o dwie linie i o żadną nową koncepcję: canActivate to nadal ta sama tablica, tylko z drugim nazwiskiem w środku, a data to zwykły obiekt, który router poniesie ze sobą i odda strażnikowi. Sam AdminComponent znowu o niczym nie wie. Ważne, żeby nie pomylić poziomów: data jest polem wpisu trasy, siostrą path i component, a nie polem wewnątrz canActivate.
Po drugiej stronie strażnik musi ten rozkaz odczytać, a wszystko, czego do tego potrzebuje, dostaje w pierwszym parametrze. Migawka trasy ma pole data z dokładnie tym obiektem, który wpisałeś w konfiguracji, więc rangę wyjmiesz zapisem route.data['role']. Trzy inne zapisy, które przychodzą w tym miejscu do głowy, są błędne z trzech różnych powodów, i warto znać każdy z nich. route.params['role'] skompiluje się bez skargi, ale zajrzy do parametrów ścieżki, czyli tam, gdzie trafiają odcinki adresu z dwukropkiem; żadnego :role w ścieżce nie ma, więc dostaniesz undefined i cichy błąd. route.query['role'] nie skompiluje się w ogóle, bo migawka nie ma pola o nazwie query - parametry zapytania siedzą w queryParams oraz queryParamMap, ale to i tak nie to miejsce, bo szukamy rozkazu z konfiguracji, a nie dopisku z adresu. route.role również się nie skompiluje: migawka nie ma pola o nazwie Twojego klucza, bo Twoje klucze mieszkają w środku obiektu data. Obie te próby przepuściłem przez kompilator i obie kończą się błędem o nieistniejącej właściwości.
Wartości w data mają typ możliwie luźny, bo Angular nie ma pojęcia, co tam wpiszesz. Żeby dalej pracować z rangą jak z tekstem, dopiszemy as string - to zapewnienie dla TypeScriptu, nazywane asercją typu, które mówi "wiem, co tu leży, potraktuj to jako napis".
// guards/role.guard.ts
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
export const roleGuard: CanActivateFn = (route) => {
const auth = inject(AuthService);
const router = inject(Router);
const requiredRole = route.data['role'] as string;
if (auth.hasRole(requiredRole)) {
return true;
}
return router.createUrlTree(['/forbidden']);
};Ten sam strażnik obsłuży teraz każdą bramę w Akademii - przy zbrojowni wpiszesz data: { role: 'kowal' } i nie napiszesz ani linijki nowego kodu. Uruchomiłem obie trasy naraz: gość z rangą shoguna wchodzi na /admin, a gość bez niej ląduje na /forbidden, przy czym adres /admin nawet nie mignie w pasku. Zwróć uwagę na to, co zostało po staremu: budowa strażnika jest identyczna jak w authGuard - ten sam typ, ten sam inject(), ten sam createUrlTree() przy odmowie. Nowa jest jedna linia, ta z odczytem rozkazu. Drugiego parametru tym razem nie wypisałem, bo adresu docelowego tu nie potrzebuję, a TypeScript na krótszą listę parametrów pozwala.
Strażnik przy wyjściu
Bramy pilnują wejścia, ale jest sytuacja odwrotna, w której też potrzebujesz strażnika. Uczeń wypełnia długi formularz zgłoszenia do klanu, ma wpisane trzy czwarte, po czym klika w nawigacji "Zbrojownia". Angular zrobi to, o co prosił: zniszczy komponent formularza i pokaże zbrojownię. Praca przepada, a użytkownik dowiaduje się o tym w chwili, gdy jest już za późno.
Do tego służy drugi rodzaj strażnika, uruchamiany nie przy wejściu, ale przy opuszczaniu trasy. Jego typ nazywa się CanDeactivateFn i różni się od poznanego wcześniej jednym: przyjmuje w pierwszym parametrze egzemplarz komponentu, który za chwilę zniknie. Angular nie wie z góry, jaki to komponent, więc typ jest ogólny - w nawiasach ostrych podajesz mu, czego się spodziewasz. Zapis CanDeactivateFn<NazwaTypu> czytaj jako "strażnik wyjścia dla komponentu opisanego przez NazwaTypu".
Nie chcemy przywiązywać strażnika do jednej klasy, bo formularzy w aplikacji będzie wiele. Zamiast tego opiszemy umowę: dowolny komponent, który potrafi odpowiedzieć na pytanie o niezapisane zmiany. Do opisywania takich umów służy w TypeScripcie interface - deklaracja mówiąca, jakie pola i metody obiekt musi mieć, bez podawania, jak są zrobione.
// guards/unsaved-changes.guard.ts
export interface CanComponentDeactivate {
hasUnsavedChanges: () => boolean;
}Ten interfejs nie tworzy niczego działającego i po kompilacji znika bez śladu - istnieje wyłącznie po to, żeby TypeScript miał czego pilnować. Czytaj go tak: komponent spełniający tę umowę ma metodę hasUnsavedChanges, która nie bierze argumentów i zwraca wartość logiczną. Komponent formularza dopisze więc implements CanComponentDeactivate i zaimplementuje tę jedną metodę, a strażnik nie będzie musiał wiedzieć o nim nic więcej.
Teraz sam strażnik. Kiedy zmiany są niezapisane, chcemy zapytać użytkownika, a nie decydować za niego. Najprościej zrobi to confirm() - wbudowane okno przeglądarki z pytaniem i dwoma przyciskami, zwracające prawdę po potwierdzeniu i fałsz po anulowaniu. To dokładnie ten typ, którego oczekuje strażnik, więc wynik można oddać wprost.
// guards/unsaved-changes.guard.ts
import { CanDeactivateFn } from '@angular/router';
export const unsavedChangesGuard: CanDeactivateFn<CanComponentDeactivate> = (component) => {
if (component.hasUnsavedChanges()) {
return confirm('Masz niezapisane zmiany. Na pewno chcesz opuscic ten ekran?');
}
return true;
};Rozkład decyzji jest tu odwrotny niż przy bramie wejściowej, więc czytaj uważnie: jeśli zmian nie ma, funkcja dochodzi do return true i wyjście odbywa się bez żadnego okienka - użytkownik, który niczego nie tknął, nie zostanie o nic zapytany. Pytamy wyłącznie wtedy, gdy jest co stracić. Prawda z confirm() znaczy "wypuść", fałsz znaczy "zostań", a przy fałszu Angular przerywa nawigację i adres w pasku wraca na trasę formularza. Zwróć uwagę, czego strażnik wyjścia nie robi: niczego nie zapisuje. Nie jest to mechanizm chroniący dane, tylko ostrzeżenie - zapis nadal należy do komponentu.
Zostaje ostatni krok, ten sam co przy bramie: strażnik musi trafić do wpisu trasy. Pole nazywa się canDeactivate, jest tablicą tak samo jak canActivate, i tak samo przyjmuje samą nazwę funkcji, bez nawiasów.
// app.routes.ts, fragment tablicy tras
{
path: 'samurai/:id/edit',
component: SamuraiEditComponent,
canDeactivate: [unsavedChangesGuard]
}Od tej chwili każde opuszczenie ekranu edycji - kliknięciem w nawigacji, przyciskiem "wstecz" w przeglądarce albo wywołaniem navigate() z kodu - przechodzi przez to pytanie. Sprawdziłem obie odpowiedzi na uruchomionym routerze: po potwierdzeniu nawigacja dochodzi do skutku, po anulowaniu adres zostaje na trasie edycji, a komponent formularza dalej żyje z całą swoją zawartością. Nie zmienia to nic w polu canActivate tej trasy ani w strażnikach wejścia - to dwa niezależne posterunki, jeden przy wejściu, drugi przy wyjściu.
Cała warta i porządek służby
Strażników w routerze jest więcej niż dwóch i łatwo pomylić ich role, bo nazwy są do siebie podobne. Zestawmy więc całą wartę w jednym miejscu, wraz z pytaniem, na które każdy z nich odpowiada.
| Strażnik | Kiedy pyta | Na jakie pytanie odpowiada |
|---|---|---|
| CanActivate | przed wejściem na trasę | czy ten użytkownik może tu wejść |
| CanActivateChild | przed wejściem na trasę-dziecko | czy może wejść głębiej, w podtrasy |
| CanDeactivate | przy próbie opuszczenia trasy | czy wolno mu stąd wyjść |
| CanMatch | przy dopasowywaniu adresu | czy ten wpis trasy w ogóle brać pod uwagę |
| Resolve | po przejściu strażników, przed aktywacją | jakie dane pobrać, zanim ekran się pokaże |
Do pytania "czy użytkownik może wejść na tę ścieżkę" odpowiada dokładnie jeden z nich i jest to CanActivate - reszta zajmuje się czym innym. CanDeactivate pilnuje kierunku odwrotnego, więc przy wejściu nie odezwie się w ogóle. Resolve nie jest właściwie strażnikiem, tylko dostawcą: nie potrafi powiedzieć "nie wpuszczam", jego zadaniem jest przynieść dane, i poznasz go dokładnie w następnej lekcji. Osobnego słowa wymaga CanLoad, na który natkniesz się w starszych poradnikach: pilnował on pobierania paczki z leniwie ładowaną trasą, ale został zastąpiony przez CanMatch i w Angularze 19 jego pole we wpisie trasy jest oznaczone jako przestarzałe. Ani jeden, ani drugi nie odpowiada na pytanie o wejście na już dopasowaną trasę - decydują o tym, czy dana trasa w ogóle wejdzie w grę.
Ustaw sobie w głowie jeszcze kolejność, w jakiej to wszystko dzieje się w życiu jednej trasy, bo bez niej łatwo o błędne założenie. Najpierw odzywa się CanActivate i decyduje, czy w ogóle wejdziesz. Dopiero gdy przepuści, ruszają resolvery po dane - i to jest sensowne w tej właśnie kolejności, bo nie ma po co ściągać z serwera zapasów dla kogoś, kogo za chwilę zawrócimy spod bramy. Potem ekran żyje swoim życiem, czasem długo. A CanDeactivate odzywa się dopiero na końcu, przy próbie wyjścia - kiedy jest już co chronić.
Pamiętaj: brama bez strażnika to tylko dziura w murze, a strażnik ma jedno zadanie - powiedzieć tak albo nie, i wskazać drogę temu, kogo zawrócił.
Hasła z dokumentacji w tej lekcji
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Który guard używamy do sprawdzenia czy użytkownik może wejść na daną ścieżkę?
2. Jakiego typu używamy dla nowoczesnego (functional) guarda chroniącego wejście na trasę?
To 2 z 5 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Układanie w poziomie
Ułóż token po tokenie podpięcie guarda do trasy admin:
- Edytor kodu
Uzupełnij functional guard sprawdzający czy użytkownik jest zalogowany.
- Układanie w pionie
Uporządkuj guardy według momentu działania w cyklu trasy (od pierwszego do ostatniego):