Wprowadzenie

Angular 8 wprowadził wiele poprawek do istniejących modułów. Z drugiej strony dodał całkowicie nowy router o nazwie Component Router. Jest to wysoce konfigurowalny komponent oferujący wiele funkcjonalności do których zaliczamy standarowe ścieżki widoków, zagnieżdzone trasy potomne, nazwane ścieżki oraz parametry przekierowań. W tym wpisie omówimy podstawy, które pozwolą na stworzenie nawigacji dla naszych użytkowników.

Pierwszym krokiem w tym wpisie jest utworzenie nowego projektu. Następnie dodamy dwa komponenty do których będziemy nawigować. Powyższe kroki możecie wykonać korzystając z dwóch poniższych wpisów:

W moim przypadku projekt oraz jego struktura prezentuje się w poniższy sposób: Angular: struktura projektu

Utworzyłem dwa podstawowe komponenty od których możemy zacząć tworzenie naszej witrny, tj. ‘Strona Główna’ oraz ‘O Nas’.

Podstawowa nawigacja

Pierwszy krok jest niezwykle prosty. Zmodyfikujemy nieznacznie widoki naszych komponentów a następnie wyświetlimy każdy z nich na stronie głównej celem sprawdzenia czy wszystko wyświetla się poprawnie. Spójrzcie poniżej: Angular: wygląd strony głównej

Kolejny krok jest już nieco bardziej skomplikowany – musimy przemyśleć jak będzie prezentowała się nawigacja po naszej witrynie. Strona główna zwykle wyświetla się jako czysty adres naszej domeny (w naszym przypadku będzie to localhost). Każda kolejna podstrona ma swój unikalny routing - zakładka ‘O Nas’ będzie wyświetlała się pod ścieżką /about.

Możemy teraz przejść do modyfikacji pliku app.component.ts lub do szablonu (’./app.component.html’) używanego przez ten plik. Ja preferuje drugie podejście ponieważ lubie mieć odseparowany kod i posługiwać się odpowiednim szablonem w celu przygotowania kodu html.

W tym kroku przygotujemy prostą nawigację, której celem będzie przekierowane nas do odpowiednich stron/podstron naszej witryny:

<ul>
    <li>
        <a [routerLink]="['/']">Strona główna</a>
    </li>
    <li>
        <a [routerLink]="['/about']">O Nas</a>
    </li>
    <div class="outer-outlet">
        <router-outlet></router-outlet>
    </div>
</ul>
Zanim przejdziemy dalej omówimy za co odpowiada powyższy kod. Dyrektywa routerLink generuje nasz link na podstawie zdefiniowanej ścieżki. Druga część zdefiniowana w selektorze div to tzw. ‘gniazdo routera’ – w tym miejscu pojawi się komponent do którego chcemy przekierować nasz widok.

Po wykonaniu powyższych kroków jesteśmy gotowi na zaimplementowanie właściwej nawigacji. W tym celu musimy zmodyfikować plik app-routing.module.ts:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

// W pierwszym kroku importujemy przygotowane komponenty
// To do nich wykonamy przekierowanie
import { HomeComponent } from './Components/home/home.component'
import { AboutComponent } from './Components/about/about.component'

// W drugim kroku tworzymy nasze ścieżki zgodnie z 
// informacjami, które zawarłem powyżej
const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Jesteśmy gotowi na przetestowanie naszej implementacji:

Udało nam się zrealizować podstawowe założenia. Nawigacja działa tak jak tego chcieliśmy. Możemy teraz przejść do kolejnych istotnych elementów tego komponentu. Tym razem nieco skomplikujemy nawigację – poniżej sekcji ‘O Nas’ dodamy kolejne dwie pod-kategorie, tj. ‘Nasze realizacje’ oraz ‘Nasze lokalizacje’.

Zagnieżdzona nawigacja

Zagnieżdzona nawigacja pozwala nam myśleć o naszej aplikacji jak o strukturze drzewa w której poszczególne komponenty są zagnieżdzone w większej liczbie komponentów.

Obecnie w naszej implementacji mamy dwie ścieżki: / oraz /about. Z uwagi na obszerność informacji zdecydowaliśmy się przygotować osobne widoki dla realizacji oraz lokalizacji. W takim wypadku przedstawimy dwie kolejne ścieżki: /about/realizations oraz /about/locations.

Struktura nawigacji po naszej witrynie będzie prezentowała się w poniższy sposób: Diagram aplikacji

Zanim jednak przejdziemy dalej dodamy dwa komponenty oraz przykładowy kod HTML. Podobnie jak w poprzednim przypadku, w pierwszym kroku, wyświetlimy wszystkie informacje w jednym komponencie celem sprawdzenia poprawności naszego kodu. Zaktualizowana struktura poniżej: Angular: nowe komponenty Oraz nowe komponenty "w akcji" : Angular: prezentacja komponentów

Pamiętacie jak chwilę wcześniej używaliśmy router-outlet? Ponownie użyjemy tego rozwiązania modyfikując plik about.component.html

<h1>O nas!</h1>
<h4>Niestety, niczego nowego nie dowiecie się z tej sekcji...:-(</h4> 

<p>To poradnik dotyczący Angulara!</p>

<ul>
    <li>
        <a [routerLink]="['/about/realizations']">Nasze realizacje</a>
    </li>
    <li>
        <a [routerLink]="['/about/locations']">Nasze lokalizacje</a>
    </li>
    <div class="outer-outlet">
        <router-outlet></router-outlet>
    </div>
</ul>

Nasz widok został odpowiednio zmodyfikowany, scieżki zostały wskazane. Musimy jeszcze odpowiednio skonfigurować router. W tym celu ponowanie wykorzystamy plik app-routing.module.ts rozbudowując naszą strukturę:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

// W pierwszym kroku importujemy przygotowane komponenty
// To do nich wykonamy przekierowanie
import { HomeComponent } from './Components/home/home.component'
import { AboutComponent } from './Components/about/about.component'
import { RealizationsComponent } from './Components/realizations/realizations.component'
import { LocationsComponent } from './Components/locations/locations.component'

// W drugim kroku tworzymy nasze ścieżki zgodnie z 
// informacjami, które zawarłem powyżej
const routes: Routes = [
  { path: '', component: HomeComponent },
  {
    path: 'about', component: AboutComponent,
    children: [
      { path: 'realizations', component: RealizationsComponent },
      { path: 'locations', component: LocationsComponent },
    ]
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
A tak prezentuje się działająca aplikacja (zwróćcie uwagę jak zmienia się URL):

Przed nami kolejne niezwykle istotne zagadnienie – przekazywanie parametrów. W momencie tworzenia aplikacji w oparciu o MVC i dodaniu kontrolera wszystko działo się automatycznie. Tutaj, na powyższe zagadanienie, poświęcimy parę chwil – dzięki temu jednak dokładnie poznamy podstawy działania.

Parametry

Wszystkie wykonane do tej pory kroki są podstawą do następnej implementacji. Przygotujemy kolejny komponent do którego będziemy dynamicznie przekazywać parametr (identifikator) naszej lokalizacji. Dzięki temu zabiegowi będziemy w stanie wyświetlić dodatkowe informacje dotyczące lokalizacji wskazanej przez użytkownika naszej witryny.

Komponent dodałem jako podkategorię dla lokalizacji: Angular: komponent detali

Doskonale wiecie, że kolejny raz musimy użyć router-outlet. Zmodyfikujemy nieznacznie widok naszych lokalizacji tak, aby po kliknięciu na wskazaną lokalizację przenieść się do widoku szczegółowego. W tym celu posłużym się znaną już dyrektywą routerLink:

<h2 style="width: 180px;margin-left: auto;margin-right: auto;">Nasze lokalizacje</h2>

<div style="width:50%;float:left">
    <h1 style="text-align: center;">
        Pallotta Teamworks
    </h1>
    <!-- Dodajmy link do naszego obrazka z odpowiednim parametrem -->
    <a [routerLink]="['/about/locations', 1]">
        <img src="https://static.boredpanda.com/blog/wp-content/uploads/2014/01/amazing-creative-workspaces-office-spaces-15-1.jpg"
            style="display:block;width:800px;margin-left:auto;margin-right:auto">
    </a>
</div>

<div style="width:50%;float:left">
    <h1 style="text-align: center;">
        Zynga
    </h1>
    <!-- Dodajmy link do naszego obrazka z odpowiednim parametrem -->
    <a [routerLink]="['/about/locations', 2]">
        <img src="https://static.boredpanda.com/blog/wp-content/uploads/2014/01/amazing-creative-workspaces-office-spaces-11-1.jpg"
            style="display:block;width:800px;margin-left:auto;margin-right:auto">
    </a>
</div>

<!-- Oraz wspomniany we wpisie kontener dla komponentu -->
<div class="inner-outlet">
<router-outlet></router-outlet>
</div>

Najgorsze za nami. Musimy jeszcze rozszerzyć przygotowaną wcześniej definicję routera tak, aby obsługiwać przekazywane parametry:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

// W pierwszym kroku importujemy przygotowane komponenty
// To do nich wykonamy przekierowanie
import { HomeComponent } from './Components/home/home.component'
import { AboutComponent } from './Components/about/about.component'
import { RealizationsComponent } from './Components/realizations/realizations.component'
import { LocationsComponent } from './Components/locations/locations.component'
import { LocationDetailsComponent} from './Components/locations/location-details/location-details.component'

// W drugim kroku tworzymy nasze ścieżki zgodnie z 
// informacjami, które zawarłem powyżej
const routes: Routes = [
  { path: '', component: HomeComponent },
  {
    path: 'about', component: AboutComponent,
    children: [
      { path: 'realizations', component: RealizationsComponent },
      { path: 'locations', component: LocationsComponent },
      { path: 'locations/:id', component: LocationDetailsComponent}
    ]
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
W liniii numer 10 importujemy komponent odpowiedzialny za wyświetlanie informacji szczegółowych dotyczących lokalizacji. Dużo bardziej istota jest linia 21 w której za pomocą zapisu :id informujemy router, że wartość parametru powinna zostać pobrana z adresu URL.

W przedostatnim kroku dokonamy modyfikacji pliku location-details.component.ts. Spójrzcie na komentarze w kodzie:

import { Component, OnInit, APP_ID } from '@angular/core';
// Importujemy klasę ActicatedRoute i wstrzykujemy ją do naszego komponentu => patrz constructor
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-location-details',
  templateUrl: './location-details.component.html',
  styleUrls: ['./location-details.component.css']
})
export class LocationDetailsComponent implements OnInit {

  // Definiujemy właściwości celem przechowywania/aktualizacji niezbędnych parametrów
  // Aby nie zaciemniać kodu dodałem tylko jedną właściwość dla opisu lokalizacji
  id: any;
  paramsSub: any;
  locationName: string;

  constructor(private activatedRoute: ActivatedRoute) { }

  ngOnInit() {
    // 1. Parametry przechowywane są w tzw. obiekcie obserwowalnym - Observable
    // 2. My subskrybujemy wszelkie zmiany. Każda zmiana wiąże się z ustawieniem odpowiedniej wartości
    //    właściwości id
    // 3. W realnym świecie identyfikator zostałyby użyty do pobrania danych z API
    // 4. Aby przeciwdziałać wyciekom pamięci używamy metody unsubsribe()
    this.paramsSub = this.activatedRoute.params.subscribe(params => this.id = parseInt(params['id'], 10));
  }

  ngOnDestroy() {
    this.paramsSub.unsubscribe();
  }

  loadDataFromDevelopmentAPI() {
    // Tutaj powinniśmy wykonać zapytanie do API - o tym w kolejnym wpisie
    // Na potrzeby tej implementacji dodamy prostą instrukcję warunkową

    if (this.id === 1) {
      this.locationName = "Pallotta Teamworks";
    } else if (this.id === 2) {
      this.locationName = "Zynga"
    } else {
      this.locationName = "Wskazana lokalizacja nie istnieje!";
    }
  }
}
Ostatni krok to proste modyfikacje szablonu HTML w celu wyświetlenia interesujących nas informacji:
<hr>
<p>Id wskazanej lokalizacji: <span>{{id}}</span></p>
<p>Nazwa wskazanej lokalizacji: <span>{{locationName}}</span></p>
<hr>

Sprawdźmy jeszcze jak wygląda działająca aplikacja:

Podsumowanie

Celem tego artykułu było wprowadzenie do podstaw nawigacji w Angularze. Przeszliśmy przez nawigację podstawową, zagnieżdzoną oraz niezwykle istotne przekazywanie parametrów. Każdy z Was ma teraz podstawy do implementacji własnych pomysłów oraz tworzenia dużych aplikacji.

Po niezwykle długiej serii wpisów dochodzimy do sedna sprawy. W kolejnym wpisie utworzymy nasz własny interfejs API w oparciu o technologię .NET Core. Utworzymy również prostą witrynę w Angularze, która będzie korzystała z danych dostarczanych przez API.