Kurs Angular · Moduł 4: Routing
Szlaki Tōkaidō - routing w Angularze
W tej lekcji13
Twoja aplikacja urosła. Jest w niej lista wojowników, jest ekran dojo, jest zbrojownia, a przełączasz je tak, jak nauczyłeś się przy anatomii komponentu: jedno pole w klasie, kilka bloków @if w szablonie, klik i widok się zmienia. Na pierwszy rzut oka wszystko gra.
Spróbuj jednak wysłać komuś ekran zbrojowni. Kopiujesz adres z paska przeglądarki, wklejasz go do wiadomości, kolega otwiera - i ląduje na ekranie startowym, bo w pasku przez cały czas stał ten sam, jeden adres. Naciśnij strzałkę wstecz: zamiast wrócić do poprzedniego widoku, wypadasz z aplikacji. Odśwież stronę: wracasz na sam początek, bo pole w klasie żyło tylko w pamięci. Jedyna rzecz, którą użytkownik zapisuje w zakładkach i podaje dalej, czyli adres, nie wie o Twojej aplikacji zupełnie nic.
W Akademii nikt nie chodzi na wyczucie. Między Edo a Kioto biegnie Tōkaidō - wielki trakt podzielony na postoje, a przy każdym postoju stoi tabliczka z nazwą. Podajesz komuś nazwę postoju, a on trafia dokładnie tam, gdzie stałeś Ty. Routing robi Ci taki trakt z paska adresu: każdemu adresowi przypisujesz jeden ekran, a Angular pilnuje, żeby adres i widok zawsze mówiły to samo. Wstecz zaczyna cofać po Twojej aplikacji, odświeżenie wraca tam, gdzie byłeś, a skopiowany adres prowadzi kolegę pod właściwą bramę.
Czego się nauczysz
- rozpisywać mapę traktu w tablicy tras
- wręczać tę mapę Angularowi jedną funkcją w pliku konfiguracji
- wyznaczać w szablonie bramę, w której pojawia się ekran aktualnej trasy
- prowadzić użytkownika linkami i podświetlać ten szlak, na którym właśnie stoi
- ustawiać postój domyślny oraz postój dla zabłąkanych
- zaznaczać w ścieżce odcinek zmienny, na przykład numer wojownika
- odsyłać ekran do osobnej paczki, ściąganej dopiero na żądanie
Mapa traktu - tablica tras
Trasa to para: pod jakim adresem i co wtedy pokazać. Angular trzyma te pary w zwykłej tablicy, a każdy jej element jest zwykłym obiektem. Tablicy nadajemy typ Routes - to nazwa udostępniana przez pakiet @angular/router, czyli obietnica złożona kompilatorowi, że w środku znajdą się wyłącznie poprawnie zbudowane trasy. Dwa pola liczą się od pierwszej minuty. Pole path to fragment adresu, pisany bez ukośnika na początku, bo ukośniki router dokłada sam. Pole component to klasa komponentu, którą trzeba pokazać, gdy adres pasuje. Umawiamy się na plik app.routes.ts i zaczynamy od dwóch najprostszych postojów.
// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { SamuraiComponent } from './samurai.component';
export const routes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: 'samurai', component: SamuraiComponent }
];Plik jest gotowy i na razie nie robi absolutnie nic - to najważniejsze zdanie tego akapitu. Powstała zwykła tablica dwóch obiektów, którą mógłbyś wypisać w konsoli i obejrzeć jak każdą inną daną. Żaden komponent nie został zmieniony, żaden ekran się nie przerysował, a przeglądarka wciąż o niczym nie wie. Zwróć uwagę na trzy szczegóły. Nazwa Routes musi przyjść z @angular/router, bo tylko ten pakiet ją eksportuje. Ukośnika przed home nie ma i być nie powinno. Dopasowanie idzie po całych odcinkach adresu, a nie po wyrażeniach regularnych, więc adres /homepage w trasę home nie trafi - to dwa różne odcinki, mimo wspólnego początku.
Wręczenie mapy - provideRouter
Mapa leżąca w szufladzie nikogo jeszcze nigdzie nie zaprowadziła. Trzeba ją wręczyć Angularowi w chwili startu aplikacji, a to miejsce już znasz z pierwszego modułu: plik main.ts woła bootstrapApplication i podaje mu obiekt konfiguracji z pliku app.config.ts. Ten obiekt ma typ ApplicationConfig i jedno interesujące nas pole - tablicę providers, czyli spis rzeczy, które aplikacja ma mieć do dyspozycji przez cały czas działania. Router dopisujemy do tego spisu funkcją provideRouter, importowaną z @angular/router. Podajesz jej swoją tablicę tras, a ona zwraca gotowy komplet wpisów do providers.
Zapamiętaj tę nazwę dokładnie, bo trzy inne brzmią wiarygodnie i żadna nie zadziała. Funkcje registerRoutes i useRouter po prostu nie istnieją - pakiet @angular/router niczego takiego nie eksportuje, a kompilator odrzuci taki import komunikatem o braku takiego eksportu. Sprawdziłem to kompilatorem, więc nie licz tu na literówkę w dokumentacji. Trzecia nazwa jest podstępniejsza: RouterModule.forRoot(routes) istnieje naprawdę i widziałeś ją w module o klanach, tyle że należy do starego świata modułów NgModule. Jej miejsce jest w tablicy imports dekoratora @NgModule, a nie w providers obiektu ApplicationConfig - typ się nie zgadza i kompilator to wyłapie. W aplikacji standalone, czyli takiej, jaką budujesz od pierwszego modułu, rejestracja routera ma dokładnie jedną postać.
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes)
]
};Od tej chwili router żyje: nasłuchuje paska adresu, czyta go po każdej zmianie i szuka w Twojej tablicy pasującej trasy. Nie zmieniło się natomiast to, co widać na ekranie - nadal nic. Router wie już, który komponent wypada pokazać, ale nikt mu nie powiedział, gdzie ma go narysować. Zwróć też uwagę, że app.routes.ts pozostał nietknięty: tablica tras nie wie o istnieniu konfiguracji, a konfiguracja tylko ją importuje. Ten rozdział ról jest celowy i bardzo wygodny, bo mapę można podmienić bez ruszania startu aplikacji.
Brama zamku - router-outlet
Brakującym ogniwem jest brama: miejsce w szablonie, przez które ekran aktualnej trasy wchodzi do środka. Angular daje na to dyrektywę o nazwie RouterOutlet. Dyrektywa to nie komponent i nie serwis, tylko dodatek, który dopisujesz do szablonu - w tym wypadku znacznik <router-outlet></router-outlet>. Router znajduje ten znacznik i tuż obok niego wstawia komponent dopasowanej trasy. To cała jego rola: wyznacza miejsce, w którym Angular renderuje komponent aktualnej ścieżki.
Warto od razu odciąć trzy podobne pomysły, bo każdy z nich brzmi rozsądnie i każdy jest nieprawdą. Po pierwsze, <router-outlet> nie służy do wyświetlania błędów routingu - ekran "nie znaleziono" to zwykły Twój komponent, podpięty do trasy, o czym za chwilę. Po drugie, nie jest dyrektywą do przekierowań - przekierowanie to pole wpisu w tablicy tras, a nie znacznik w szablonie. Po trzecie, nie jest serwisem do nawigacji - serwis o nazwie Router, którym wydaje się rozkaz przejścia z poziomu kodu, poznasz na następnym postoju. Bramę widać w szablonie, więc RouterOutlet trzeba dopisać do tablicy imports komponentu standalone, dokładnie tak jak każdy inny element używany w szablonie.
// app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet],
template: `
<main>
<router-outlet></router-outlet>
</main>
`
})
export class AppComponent { }Teraz wejście na adres /home naprawdę pokaże HomeComponent, a wejście na /samurai podmieni go na SamuraiComponent. Najciekawsze jest jednak to, co się przy tym nie dzieje. Sam AppComponent nie jest tworzony na nowo - żyje przez cały czas, więc znacznik <main>, a razem z nim każdy nagłówek czy stopka, które byś dopisał wokół bramy, zostaje na ekranie nietknięty. Przeglądarka nie przeładowuje strony, nie ma białego mignięcia, nie ma ponownego pobierania aplikacji. Wymienia się wyłącznie ten jeden fragment przy bramie.
Tabliczki przy trakcie - routerLink
Skoro adresy działają, przydałoby się dać użytkownikowi coś do kliknięcia. Odruch podpowiada zwykły odnośnik z atrybutem href, i on nawet zadziała - tyle że katastrofalnie. Przeglądarka potraktuje href jak żądanie zupełnie nowego dokumentu: porzuci całą aplikację, pobierze ją od nowa, uruchomi jeszcze raz i skasuje wszystko, co trzymałeś w pamięci. Zamiast płynnego przejścia dostaniesz pełne przeładowanie. Dlatego do linków wewnątrz aplikacji służy dyrektywa routerLink, która przechwytuje kliknięcie, powstrzymuje przeglądarkę i oddaje sprawę routerowi. Ona też musi trafić do tablicy imports. Istnieje jeszcze druga droga - metoda navigate serwisu Router, wywoływana z kodu klasy - ale to metoda, a nie dyrektywa szablonu, i zajmiemy się nią na następnym postoju.
// app.component.ts
import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, RouterLink],
template: `
<nav>
<a routerLink="/home">Dom</a>
<a routerLink="/samurai">Samurajowie</a>
</nav>
<main>
<router-outlet></router-outlet>
</main>
`
})
export class AppComponent { }Kliknięcie w "Dom" zmienia teraz adres w pasku na /home i podmienia zawartość bramy, a strzałka wstecz przenosi z powrotem na poprzednią trasę. Zwróć uwagę na budowę takiego odnośnika, bo składasz go zawsze z tych samych czterech kawałków w tej samej kolejności: otwarcie znacznika <a, potem atrybut routerLink="/home", potem zamknięcie znacznika otwierającego >, potem widoczny napis, a na końcu </a>. Nie zmienił się przy tym plik app.routes.ts ani nic w konfiguracji - mapa traktu jest ta sama, dołożyłeś jedynie tabliczki, które na nią wskazują.
Podświetlona tabliczka - routerLinkActive
Nawigacja działa, ale ma wadę widoczną gołym okiem: wszystkie tabliczki wyglądają identycznie i użytkownik nie wie, na którym postoju stoi. Angular rozwiązuje to dyrektywą routerLinkActive. Podajesz jej nazwę klasy CSS, a ona dokłada tę klasę do odnośnika przez cały czas, gdy trasa spod routerLink jest aktywna, i zdejmuje ją, gdy przestaje. Jest jeden haczyk: domyślnie porównanie idzie po początku adresu, więc link do /samurai pozostaje podświetlony także wtedy, gdy stoisz na /samurai/7. Czasem tego właśnie chcesz, a czasem nie. Sterujesz tym opcją routerLinkActiveOptions z polem exact: wartość true każe wymagać zgodności całego adresu. Zapisujemy ją w nawiasach kwadratowych, czyli wiązaniem właściwości, które poznałeś przy komunikacji wojowników - kwadratowe nawiasy mówią Angularowi, że w cudzysłowie jest wyrażenie do obliczenia, tutaj obiekt, a nie zwykły napis.
<nav>
<a routerLink="/home"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }">
Dom
</a>
<a routerLink="/samurai" routerLinkActive="active">
Samurajowie
</a>
</nav>Po tej zmianie odnośnik do listy wojowników dostaje klasę active również na trasie /samurai/7, a odnośnik do domu podświetla się wyłącznie na samym /home. Uwaga na dwie rzeczy, które nie zaszły. Angular nie nadał tej klasie żadnego wyglądu - słowo active to nazwa, którą wymyślasz sam i którą musisz opisać w arkuszu stylów, inaczej nic nie zobaczysz. Nie zmieniło się też nic w tablicy tras: podświetlanie jest wyłącznie sprawą szablonu. Pamiętaj również, żeby dopisać RouterLinkActive do tablicy imports komponentu, obok RouterLink i RouterOutlet.
Postój domyślny - pusty adres
Zostaje pytanie, od którego zaczyna każdy użytkownik: co widzi ktoś, kto wpisał samą domenę? Adres jest wtedy pusty, a pustej ścieżki w Twojej mapie nie ma. Zapisujemy ją jako path: '' i mamy dwa wyjścia. Można przypiąć do niej komponent, tak jak do każdej innej trasy, czyli zapisać { path: '', component: HomeComponent } i mieć ekran startowy pod gołą domeną. Można też odesłać użytkownika gdzie indziej polem redirectTo, w którym podajesz adres docelowy. Przy przekierowaniu z pustej ścieżki dochodzi trzecie pole, pathMatch, i tu tkwi cała subtelność. Domyślną wartością jest 'prefix', czyli "wystarczy, że adres tak się zaczyna" - a pusty napis zaczyna każdy adres na świecie, więc taka trasa przechwytywałaby dosłownie wszystko. Angular zna tę pułapkę i nie pozwala jej zastawić: pustą ścieżkę z redirectTo bez podanego pathMatch odrzuca jako błędną konfigurację, jeszcze zanim dojdzie do pierwszej nawigacji, a komunikat błędu wprost prosi o uzupełnienie tego pola. Wartość 'full' znaczy "cały pozostały adres ma być pusty" i tylko ona jest tu sensowna. Trasę składasz z czterech kawałków po kolei: otwarcie { path: '',, potem redirectTo: '/home',, potem pathMatch: 'full', na końcu zamykający }.
// app.routes.ts, importy jak wyzej
export const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'samurai', component: SamuraiComponent }
];Wejście na sam adres domeny prowadzi teraz na /home i to właśnie /home widnieje w pasku. Całość jest przy tym jedną nawigacją, więc w historii przeglądarki nie zostaje osobny wpis dla pustego adresu - sprawdziłem to i po takim przejściu przybywa dokładnie jeden wpis, ten docelowy. Strzałka wstecz nie ma więc szans wpaść w kółko między pustym adresem a celem przekierowania. Nie zmieniła się przy tym trasa home: ona nadal jest zwykłym postojem z komponentem i o przekierowaniu nic nie wie. Zapamiętaj jeszcze wyjątek, który oszczędzi Ci niepotrzebnego pisania: gdy pusta ścieżka prowadzi wprost do komponentu, a nie do przekierowania, pole pathMatch nie jest potrzebne - router i tak nie uzna trasy za dopasowaną, dopóki w adresie zostaje niezużyty odcinek.
Zabłąkani na trakcie - trasa wildcard
Prędzej czy później ktoś wpisze adres, którego w mapie nie ma: literówka w linku, stara zakładka, ciekawski użytkownik. Bez przygotowania kończy się to pustym ekranem i wyjątkiem w konsoli - router mówi wprost, że nie potrafi dopasować żadnej trasy, i przerywa nawigację. Dlatego na końcu mapy stawia się postój dla zabłąkanych, po angielsku wildcard. Jego ścieżka to '**', czyli dwie gwiazdki, i pasuje do dowolnej liczby pozostałych odcinków adresu. Trzy inne zapisy nie zadziałają i warto wiedzieć dlaczego. Ścieżki '404' oraz 'notfound' to całkiem zwykłe trasy, które dopasują się wyłącznie do adresów /404 i /notfound - a przecież nie o takie adresy się rozbijamy. Pojedyncza gwiazdka '*' nie ma w Angularze żadnego magicznego znaczenia: router potraktuje ją jak zwyczajny odcinek o nazwie * i dopasuje tylko dosłowny adres /*. Kolejność też nie jest dowolna, bo router czyta tablicę z góry na dół i bierze pierwsze trafienie - wildcard postawiony wyżej przechwyciłby wszystko, co pod nim.
// app.routes.ts, importy jak wyzej plus NotFoundComponent
export const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'samurai', component: SamuraiComponent },
{ path: '**', component: NotFoundComponent }
];Adres /kioto pokaże teraz Twój ekran "nie znaleziono" zamiast wyjątku, a wszystkie wcześniejsze trasy działają dokładnie tak jak przedtem, bo wildcard dostaje szansę dopiero wtedy, gdy nic nad nim nie pasowało. Zauważ też, czego wildcard nie robi: nie zmienia kodu odpowiedzi serwera i nie jest prawdziwym błędem 404 w rozumieniu protokołu - to zwykły ekran Twojej aplikacji. Jeśli nie chcesz robić osobnego komponentu, wildcard może po prostu odesłać użytkownika na start zapisem { path: '**', redirectTo: '' }, a wtedy zabłąkany trafi tam, gdzie prowadzi trasa domyślna.
Odcinek zmienny - parametr w ścieżce
Twój klan liczy trzystu wojowników i każdy ma własny ekran z profilem. Trzystu wpisów w tablicy nikt nie napisze, a gdyby napisał, to przy nowym rekrucie trzeba by wracać do pliku. Adresy /samurai/7 i /samurai/128 różnią się przecież tylko ostatnim odcinkiem. Angular pozwala ten odcinek oznaczyć jako zmienny: wystarczy postawić przed jego nazwą dwukropek. Zapis samurai/:id czyta się jako "odcinek samurai, a po nim cokolwiek, i nazwijmy to coś id". Uważaj na zapisy z innych ekosystemów, bo palce same je piszą. Klamry {id} znasz z serwerów w rodzaju Express, nawiasy kwadratowe [id] z routingu po plikach w Next.js, a $id z szablonów tekstowych. W Angularze żaden z nich nie jest parametrem - router weźmie taki zapis dosłownie i będzie szukał odcinka o nazwie {id} albo [id].
// app.routes.ts, importy jak wyzej plus SamuraiDetailComponent
export const routes: Routes = [
{ path: 'samurai', component: SamuraiComponent },
{ path: 'samurai/:id', component: SamuraiDetailComponent },
{ path: '**', component: NotFoundComponent }
];Jedna trasa obsługuje teraz wszystkich wojowników naraz: /samurai/7 i /samurai/128 trafiają w ten sam wpis i pokazują ten sam komponent. Nie ucierpiała przy tym trasa samurai bez numeru - lista wciąż działa, bo adres bez ostatniego odcinka pasuje wyłącznie do niej. Jest jednak rzecz, której ten kod nie robi, i to najważniejsze zdanie tej sekcji: router nie wkłada numeru do komponentu. SamuraiDetailComponent na razie nie ma pojęcia, czy otwarto go dla siódemki, czy dla stu dwudziestu ośmiu. Odczytanie tej wartości to temat następnej lekcji.
Oddziały na żądanie - loadComponent
Wszystkie komponenty wymienione dotąd w mapie mają jedną wspólną cechę: są importowane u góry pliku, więc trafiają do pierwszej paczki, którą przeglądarka ściąga przy starcie. Przy trzech ekranach to bez znaczenia, przy trzydziestu użytkownik czeka na kod, którego być może nigdy nie zobaczy. Zamiast pola component możesz podać pole loadComponent - funkcję, która dopiero po wejściu na trasę sięgnie po plik i zwróci z niego klasę. Sięganie robi wyrażenie import() z nawiasami, ładujące plik w trakcie działania aplikacji i oddające obietnicę, czyli Promise. Z tej obietnicy wyjmujemy klasę metodą then, podając krótką funkcję c => c.ForgeComponent. Dwie sąsiednie nazwy są pułapkami: lazyComponent nie istnieje i kompilator odrzuci taki wpis jako nieznane pole, a zwykłe component działa, tylko dokładnie odwrotnie - ściąga kod od razu.
// app.routes.ts, fragment tablicy tras
{
path: 'forge',
loadComponent: () => import('./forge/forge.component')
.then(c => c.ForgeComponent)
},Z punktu widzenia użytkownika nie zmieniło się nic: adres /forge prowadzi do tego samego ekranu, tabliczka routerLink działa tak samo, wildcard nadal łapie zabłąkanych. Zmienił się wyłącznie moment pobrania kodu - paczka z kuźnią zjeżdża z serwera dopiero przy pierwszym wejściu na trasę, a start aplikacji jest o tyle lżejszy. Zwróć też uwagę, że u góry pliku nie ma importu ForgeComponent. Gdybyś go dopisał, klasa wróciłaby do głównej paczki i cały zysk by wyparował.
Cały odcinek na żądanie - loadChildren
Zbrojownia to nie jeden ekran, tylko cała sekcja: spis katan, karta pojedynczej broni, formularz zamówienia u kowala. Odsyłanie ich pojedynczo byłoby mozolne, a przy okazji chciałoby się trzymać ich trasy w osobnym pliku, bliżej samej sekcji. Do tego służy pole loadChildren, bliźniak loadComponent, z jedną różnicą: zamiast klasy komponentu wyciągasz z pliku tablicę tras. Zaczynamy więc od takiego pliku. Wewnątrz niego ścieżki liczą się względem rodzica, więc pusta ścieżka oznacza tu wejście na samą zbrojownię.
// weapons/weapons.routes.ts
import { Routes } from '@angular/router';
import { WeaponsListComponent } from './weapons-list.component';
import { WeaponDetailComponent } from './weapon-detail.component';
export const WEAPONS_ROUTES: Routes = [
{ path: '', component: WeaponsListComponent },
{ path: ':id', component: WeaponDetailComponent }
];Powstała druga, mniejsza mapa - zupełnie samodzielna i na razie z niczym niepołączona. Nie ma w niej żadnego provideRouter, bo ten wywołuje się raz na całą aplikację, i nie ma powtórzonego odcinka weapons, bo dokleją go trasy rodzica. Teraz wystarczy wskazać na nią z mapy głównej. Adres /weapons trafi wtedy na pustą ścieżkę z pliku, który przed chwilą napisałeś, czyli na spis broni, a /weapons/12 na kartę pojedynczej katany.
// app.routes.ts, fragment tablicy tras
{
path: 'weapons',
loadChildren: () => import('./weapons/weapons.routes')
.then(r => r.WEAPONS_ROUTES)
},Sklejanie odcinków dzieje się samo: router zdejmuje z adresu weapons, a resztę oddaje mniejszej mapie. Nie zmieniło się nic w pliku zbrojowni - ta sama tablica zadziałałaby pod innym odcinkiem, gdybyś zmienił tu jedno słowo, i to jest cała zaleta takiego podziału. Kiedy sięgać po co? Polecam Ci prostą regułę: pojedynczy ekran bez własnych podstron odsyłaj przez loadComponent, bo zapis jest krótszy i nie mnoży plików; całą sekcję z własnym podziałem na ekrany odsyłaj przez loadChildren, bo wtedy jej trasy mieszkają razem z jej kodem.
Cała mapa razem
Czas zestawić wszystko w jednym pliku, bo dopiero wtedy widać rzecz najważniejszą - kolejność. Router czyta tablicę z góry na dół i zatrzymuje się na pierwszej pasującej trasie, więc wpisy szczegółowe muszą stać przed ogólnymi, a wildcard zawsze na samym końcu. Przekierowanie z pustej ścieżki wypada na początku, żeby rzucało się w oczy przy czytaniu pliku.
// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { SamuraiComponent } from './samurai.component';
import { SamuraiDetailComponent } from './samurai-detail.component';
import { NotFoundComponent } from './not-found.component';
export const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'samurai', component: SamuraiComponent },
{ path: 'samurai/:id', component: SamuraiDetailComponent },
{
path: 'forge',
loadComponent: () => import('./forge/forge.component')
.then(c => c.ForgeComponent)
},
{
path: 'weapons',
loadChildren: () => import('./weapons/weapons.routes')
.then(r => r.WEAPONS_ROUTES)
},
{ path: '**', component: NotFoundComponent }
];Przeczytaj tę mapę jak rozkład postojów na trakcie: pusty adres odsyła na dom, cztery odcinki mają przypisane ekrany, dwa dalsze ściągane są dopiero na żądanie, a ostatni wpis łapie każdego, kto zabłądził. Zwróć uwagę, czego tu nie ma. Nie ma ani jednego import dla kuźni i zbrojowni, bo te wjeżdżają leniwie. Nie ma też żadnej wzmianki o wyglądzie, o bramie ani o tabliczkach - mapa mówi wyłącznie "jaki adres, taki ekran", a reszta należy do szablonu i do pliku konfiguracji.
Anatomia adresu
Na koniec spójrz na sam adres, bo niesie więcej niż jeden rodzaj informacji i w kolejnych lekcjach będziesz z niego czytał. Odcinki oddzielone ukośnikami są obowiązkowe i biorą udział w dopasowaniu trasy - to one decydują, który ekran się otworzy. Po znaku zapytania idą dopiski nieobowiązkowe, w dopasowaniu nieuczestniczące, dobre na sortowanie czy filtry. Po krzyżyku stoi fragment, czyli wskazanie miejsca wewnątrz otwartego już ekranu.
| Część adresu | Przykład | Rola |
|---|---|---|
| Odcinek ścieżki | /samurai/17 | Obowiązkowy, wybiera trasę |
| Dopisek po znaku zapytania | /samurai?rank=hatamoto | Nieobowiązkowy, nie zmienia trasy |
| Fragment po krzyżyku | /samurai#kodeks | Wskazuje miejsce w otwartym ekranie |
Dziś rozrysowałeś trakt i postawiłeś bramę - odczytywaniem tego, co niesie sam adres, zajmiemy się na następnym postoju. Pamiętaj: mapa tras mówi tylko "jaki adres, taki ekran", a wszystko dalsze zaczyna się dopiero wtedy, gdy staniesz na właściwym postoju.
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Co to jest <router-outlet> w Angular?
2. Którą funkcją rejestrujemy router w app.config.ts (standalone Angular)?
To 2 z 6 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Uzupełnij konfigurację routingu z trasami do HomeComponent i SamuraiComponent.
- Klikanie w kolejności
Zbuduj znacznik <a> z linkiem routera podświetlanym jako aktywny:
- Układanie w poziomie
Ułóż token po tokenie trasę domyślnego przekierowania na /home: