Dzisiaj chce przedstawić jak w szybki sposób dodać routing do naszej aplikacji z frameworkiem Angular. Podczas tego procesu można natknąć się na kilka problemów, o których tutaj wspomnę. Do dzieła!
Routing krok po kroku
Moje przykłady podaję na projekcie, który powstał przy użyciu dotnet CLI. Po zainstalowaniu .NET Core SDK, w konsoli wpisujemy dotnet new --install Microsoft.AspNetCore.SpaTemplates::*
. Daje nam to dostęp do wielu szablonów projektów. Następnie wpisujemy dotnet new angular
i mamy gotowy projekt z którym można pracować! Po więcej informacji na ten temat odsyłam na bloga jednego z autorów tych szablonów, Steve’a Sandersona. Opisał to wszystko ładnie w tym poście.
Wracając do routingu, na początek definiujemy moduł, w którym powiemy aplikacji jak ma się zachowywać, gdy użytkownik będzie chciał wejść w podany przez niego adres na naszej stronie. Później zaimportujemy go do głównego modułu, który jest uruchamiany jako pierwszy. Moduł routingu składa się z kilku części. Na początek importujemy potrzebne komponenty:
1 2 3 4 5 6 7 |
--Import komponentów frameworka import { NgModule } from '@angular/core'; import { Routes, RouterModule, PreloadAllModules } from '@angular/router'; import { MainComponent } from './main.component'; import { CatComponent } from './cat/cat.component'; |
Dekorator NgModule
potrzebny jest do zdefiniowania modułu. Routes
oraz RouterModule
potrzebne są już do napisania kodu, który pozwoli nam na nawigację wewnątrz aplikacji. Następnie importowaliśmy komponenty, które sami dodaliśmy i chcemy wyświetlić. Następnie pora na przekazanie aplikacji jak ma się zachowywać, czyli definicję ścieżek.
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 |
-- Routes const routes: Routes = [ { path: '', component: MainComponent, children: [ { path: '', component: CatComponent, }, { path: 'dog', loadChildren: './dog/dog.module#DogModule' } ] } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } export const routableComponents = [ MainComponent, CatComponent ]; |
Import RouterModule.forRoot(routes)
załatwi nam sprawę routingu.
Dzięki takiej definicji, po otwarciu aplikacji, od razu zobaczymy komponent CatComponent
. Został on jawnie zaimportowany na starcie aplikacji. W tym przykładzie pokażę także jeszcze jedną ważną sprawę na którą trzeba zwrócić uwagę, w momencie gdy dodajemy routing do naszej aplikacji. CatComponent
został pobrany od razu gdy weszliśmy na stronę aplikacji, mogło to wpłynąć na czas otwarcia aplikacji. Takie podejście nazywamy EagerLoading. Jeżeli byłby on duży, mogłoby to w znaczący sposób wpłynąć na to, jak użytkownik będzie odbierał naszą aplikację. Z kolei DogModule
zostanie załadowany dopiero wtedy, gdy będziemy chcieli wejść do niego. Będzie wtedy można zauważyć małe opóźnienie przed otwarciem tej strony. Ta metoda z kolei nazywana jest LazyLoading. Dzięki temu, że komponent załadowany będzie później, cała strona będzie ładować się szybciej. Coś za coś, idziemy tutaj na kompromis. Jednak nie musi tak być. Wcześniej w imporcie komponentów z @angular/router
zaimportowałem jeszcze klasę PreloadAllModules
. Jeżeli definicję modułu zmienimy na następującą:
1 2 3 4 5 6 7 |
--Lazy loading @NgModule({ imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })], exports: [RouterModule] }) export class AppRoutingModule { } |
Moduł DogModule
, oraz wszystkie inne które tak dodamy do routingu pobrane zostaną w tle po uruchomieniu aplikacji. Dzięki temu zyskujemy zarówno szybkość ładowania całej aplikacji, jak i responsywność podczas przechodzenia pomiędzy stronami. Ważna uwaga: żeby LazyLoading działał w projekcie wykorzystującym Webpacka, musimy zainstalować paczkę angular-router-loader
, a następnie użyć ją w pliku webpack.config.js podczas konfiguracji plików .ts
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
-- angular-router-loader --config... module: { rules: [ { test: /\.ts$/, include: /ClientApp/, use: [ 'awesome-typescript-loader?silent=true', 'angular2-template-loader', 'angular-router-loader' ] }, --config... }, |
Kolejnym istotnym punktem jest stała routableComponents
którą eksportujemy razem z modułem. Dzięki temu że tutaj dodajemy moduły, nie trzeba będzie ich na nowo importować w głównym module.Należy pamiętać, że każdy komponent który jest używany musi być dodany do declarations
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
-- Main module of application import { NgModule } from '@angular/core'; import { UniversalModule } from 'angular2-universal'; import { AppRoutingModule, routableComponents } from './app.routing.module'; @NgModule({ imports: [ UniversalModule, AppRoutingModule ], declarations: [ routableComponents ], bootstrap: [MainComponent], }) export class AppModule { } |
Teraz pozostaje tylko dodać moduł DogModule
:
1 2 3 4 5 6 7 8 9 10 |
--Dog Module import { NgModule } from '@angular/core'; import { DogRoutingModule, routableComponents } from './dog.routing.module'; @NgModule({ imports: [DogRoutingModule], declarations: [routableComponents] }) export class DogModule { } |
Oraz moduł routingu DogRoutingModule
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
--Dog routing module import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { DogComponent } from './dog.component'; const routes: Routes = [{ path: '', component: DogComponent }]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class TutorialRoutingModule { } export const routableComponents = [DogComponent]; |
Dla takich małych modułów może się wydawać, że to spory narzut, jeżeli chodzi o ilość pracy. Ale jeżeli nasze komponenty są bardziej rozbudowane i mają swoje ścieżki podrzędne, dzięki temu nasz główny komponent nie rozrośnie się do niebotycznych rozmiarów. Dodajemy także separację do logiki naszej aplikacji, co wpływa na przejrzystość kodu.
Na koniec dodajemy nasz MainComponent
, czyli główną stronę aplikacji:
1 2 3 4 5 6 7 8 |
--Main component import { Component } from '@angular/core'; @Component({ selector: 'app', templateUrl: './main.component.html' }) export class MainComponent { } |
Oraz szablon dla tego komponentu:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
-- Main component template <div> <h1>My app</h1> <nav> <ul> <li><a [routerLink]="['/']" >Cats</a></li> <li><a [routerLink]="['/dog']" >Dogs</a></li> </ul> </nav> <router-outlet></router-outlet> </div> |
W szablonie możemy zobaczyć dwa ważne elementy: routerLink
oraz router-outlet
. routerLink
to odpowiednik ng-href
z pierwszego Angulara, czyli po prostu doda nam odpowiedni adres dla odnośnika na podstawie podanej nazwy ścieżki. router-outlet
to najważniejszy element tutaj, czyli miejsce gdzie wstawione zostaną nasze komponenty, w poprzedniej wersji ng-view
. Dodatkowo należy pamiętać także o dodaniu do znacznika head
wartość <base href="/">
, bez tego routing nie zadziała.
Podsumowanie
Przedstawiłem tutaj jedynie część możliwości jakie daje nam Angular jeżeli chodzi o routing. W kolejnych postach przedstawię bardziej skomplikowane przypadki: przekazywanie parametrów w adresie scieżki, RouteGuards, czyli mechanizm pozwalający na np. blokowanie wejścia do niektórych fragmentów aplikacji oraz wykorzystanie więcej niż jednego router-outlet
.