Witam dziś zajmiemy się routingiem. Najpierw w tej części napiszemy główny routing aplikacji, następnie w drugiej, podzielimy go na moduły i podepniemy wszystkie komponenty. Napiszemy też „guarda”, który będzie bronił dostępu do niektórych części aplikacji, np kiedy nie jesteś zalogowany.  W 3 części zajmiemy się parametrami routingu. Zabieramy się do pracy.


Czym rożni się routing aplikacji SPA od zwykłej strony internetowej.  Podstawową różnicą jest, że w aplikacji SPA routing podstawia nam komponenty w jednym widoku natomiast w typowych stronach internetowych mechanizm routingu kieruje nas na osobną stronę. Ma to swoje plusy i minusy. Niewątpliwym  plusem typowego podejścia do routingu w „zwykłych” stronach internetowych jest większe wsparcie SEO. Jednakże przy tworzeniu aplikacji angularowych nie jest nam to potrzebne. Jeśli ktoś jest zainteresowany tym tematem odsyłam do artykułów w notatkach pod postem. Drugą ważną różnicą jest to, że routing w SPA nie przeładowuje nam strony, przypomina to AJAX’a i ładowanie przez niego danych.

Obecny stan aplikacji wygląda tak: https://github.com/taaaniel/foodCalc/tree/part_6_translation_service_end

Na początku dodamy do aplikacji trochę komponentów i podzielimy je na mniejsze. Zajmijmy się na początek dashboardem. Sam dashboard to dość spory komponent,  który na razie nie ma swojego html.  Cały jego kod znajduje się w body.component.html a zatem przenieśmy część widoku z bodu do dashboard, a w body.component.html zostawmy referencje do dashboardu.

Plik dashboard.component.html:

<mat-card-header>
  <div mat-card-avatar>
    <mat-icon mat-list-icon>accessibility</mat-icon>
  </div>
  <mat-card-title>{{'BODY.MY_BALANCE_OF_THE_DAY' | translate: lang}}</mat-card-title>
  <mat-card-subtitle>
    <mat-chip-list>
      <mat-chip>{{'BODY.CALORIES' | translate: lang}}: 2500</mat-chip>
      <mat-chip>{{'BODY.CARBOHYDRATES' | translate: lang}}: 120</mat-chip>
      <mat-chip color="primary" selected="true">{{'BODY.PROTEINS' | translate: lang}}: 23</mat-chip>
      <mat-chip color="accent" selected="true">{{'BODY.FATS' | translate: lang}}: 340</mat-chip>
    </mat-chip-list>
  </mat-card-subtitle>
</mat-card-header>
<mat-card-content>
  <p>
      {{'BODY.CHOOSE_A_MEAL_TIME_AND_ADD_PRODUCTS' | translate: lang}}.
      {{'BODY.COUNT_CALORIES_AND_CHECK_YOUR_DUMMIES_NUTRIENT_BALANCE_SHEETS' | translate: lang}}
  </p>
  <mat-tab-group>
    <mat-tab label="{{'BODY.BREAKFAST' | translate: lang}}">Content 1</mat-tab>
    <mat-tab label="{{'BODY.SECOND_BREAKFAST' | translate: lang}}">Content 2</mat-tab>
    <mat-tab label="{{'BODY.LUNCH' | translate: lang}}">Content 2</mat-tab>
    <mat-tab label="{{'BODY.TEA' | translate: lang}}">Content 2</mat-tab>
    <mat-tab label="{{'BODY.DINNER' | translate: lang}}">Content 2</mat-tab>
    <mat-tab label="{{'BODY.ELEVENSES' | translate: lang}}">Content 2</mat-tab>
  </mat-tab-group>
</mat-card-content>

a plik body.component.html:

<mat-card class="example-card">
  <app-dashboard></app-dashboard>
</mat-card>

Gdy to zrobimy pojawią się nam błędy :

Uncaught Error: Template parse errors:
‚app-dashboard’ is not a known element

jest to spowodowane tym, że dashboard.module.ts nie posiada zaimportowanego SharedModule gdzie są odniesienia do Materiala. Powinniśmy także wyeksportować DashboardComponent, gdyż będziemy go używać w body.component.html, który to jest w innym module. Natomiast w home.module.ts powinniśmy zaimportować DashboardModule, z którego będziemy korzystać.

Uwaga, nie jest to może bardzo dobra praktyka, ale im więcej zrobimy sobie zamieszania z komponentami w tutorialu czy treningu to szybciej wejdzie nam to w krew.

dashboard.module.ts wygląda tak:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DashboardComponent } from './dashboard.component';
import { SharedModule } from '../shared/shared.module';

@NgModule({
  declarations: [DashboardComponent],
  imports: [
    CommonModule,
    SharedModule
  ],
  exports: [
    DashboardComponent
  ]
})
export class DashboardModule { }

a home.module.ts :

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HomeComponent } from './home.component';
import { SharedModule } from '../shared/shared.module';
import { HeaderComponent } from './header/header.component';
import { BodyComponent } from './body/body.component';
import { FooterComponent } from './footer/footer.component';
import { SidebarComponent } from './sidebar/sidebar.component';
import { DashboardModule } from '../dashboard/dashboard.module';

@NgModule({
  declarations: [HomeComponent, HeaderComponent, BodyComponent, FooterComponent, SidebarComponent],
  imports: [
    CommonModule,
    SharedModule,
    DashboardModule
  ],
  exports: [
    HomeComponent
  ]
})
export class HomeModule { }

Gdy skompilujemy aplikację, wszystko wygląda już znajomo, ale nie ma tłumaczeń. Zgodnie z poprzednią częścią kursu, powinniśmy wstrzyknąć do kontrolera komponentu, w którym są słowa do tłumaczenia z filtrem,  serwis translacyjny. Plik dashboard.component.ts wygląda teraz tak:

import { Component, OnInit } from '@angular/core';
import { LocaleService, TranslationService, Language } from 'angular-l10n';


@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {

  @Language() lang: string;

  constructor(
    public locale: LocaleService,
    public translation: TranslationService
  ) { }


  ngOnInit() {
  }

}

Teraz dodamy nowe komponenty: login, product. Zmienimy nazwę linku w sidebarze i dodamy nowy. W konsoli kiedy jesteśmy w katalogu z aplikacja korzystając z Angular CLI wpisujemy:

ng generate component login

A następnie tworzymy komponent produkt:

ng generate component product

Zmieńmy jeszcze komponent sidebar.component.html dodając tam nowy link i edytując pierwszy. Wygląda to tak:

<section class="sidebar" fxFlex="220px">
  <div class="main-menu-header">
    <div fxLayout='row' fxFlex="200px">
      <div fxFlex="33%">
        <button mat-mini-fab>
          <mat-icon color="primary">message</mat-icon>
        </button>
      </div>
      <div fxFlex="33%">
        <button mat-mini-fab>
          <mat-icon>favorite</mat-icon>
        </button>
      </div>
      <div fxFlex="33%">
        <button mat-mini-fab>
          <mat-icon>list</mat-icon>
        </button>
      </div>
    </div>
  </div>
  <div class="main-menu">
    <mat-list>
      <h3 mat-subheader>{{'SIDEBAR.NAVIGATION' | translate: lang}}</h3>
      <mat-list-item>
        <mat-icon mat-list-icon>dashboard</mat-icon>
        <h4 mat-line>
          <a href="#">{{'SIDEBAR.DASHBOARD' | translate: lang}}</a>
        </h4>
      </mat-list-item>
      <mat-list-item>
        <mat-icon mat-list-icon>restaurant</mat-icon>
        <h4 mat-line>
          <a href="#">{{'SIDEBAR.MY_DASHES' | translate: lang}}</a>
        </h4>
      </mat-list-item>
      <mat-list-item>
        <mat-icon mat-list-icon>fastfood</mat-icon>
        <h4 mat-line>
          <a href="#">{{'SIDEBAR.MY_PRODUCTS' | translate: lang}}</a>
        </h4>
      </mat-list-item>
      <mat-divider></mat-divider>
      <h3 mat-subheader>Notes</h3>
      <mat-list-item>
        <mat-icon mat-list-icon>note</mat-icon>
        <h4 mat-line>
          <a href="#">{{'SIDEBAR.NOTES' | translate: lang}}</a>
        </h4>
      </mat-list-item>
    </mat-list>
  </div>
</section>

W powyższym kodzie możemy zauważyć, że zmieniły nam się klucze :

„SIDEBAR.MY_DISHES” i „SIDEBAR.MY_PRODUCTS”. Dodajemy je do słowników i tłumaczymy.

Zabieramy się teraz za routing. Tak jak wspomniałem wyżej , w tej lekcji pokażemy bardzo prosty routing. Zagadnienie to wymaga więcej wiedzy, dlatego rozbijemy to na parę lekcji.

W pliku app.module.ts w tablicy „imports” wpisujemy „RouterModule”, aby zaimportować moduł angularowego routera.

RouterModule posiada taką metodę, do której dostajemy się po kropce.  Ta metoda każe stworzyć tablicę obiektów zawierających ścieżkę i komponent, który ma się otwierać gdy będziemy na takiej ścieżce. Całość wyglądać będzie następująco:

RouterModule.forRoot([
      { path: 'login', component: LoginComponent },
      { path: 'dashboard', component: DashboardComponent },
      { path: 'dish', component: DishComponent},
      { path: 'product', component: ProductComponent}
    ])

Przy próbie kompilacji pojawią się błędy:

Uncaught Error: Component DishComponent is not part of any NgModule or the module has not been imported into your module.

Dlatego powinniśmy zaimportować DishModule, gdyż używamy DishComponent który jest w DishModule.

Teraz w body.component.html musimy wywalić nasz selector app-dashboard i podmienić go na specjalny selektor angularowego routingu:

<mat-card class="example-card">
  <router-outlet></router-outlet>
</mat-card>

Jest to specjalny selektor który reprezentuje miejsce gdzie będą wrzucane odpowiednie komponenty w zależności od tabeli routów jaki napisaliśmy, czyli jeśli adres będzie taki:

http://localhost:4200/dashboard to pokaże się komponent dashboard, jeśli http://localhost:4200/dish to będziemy mieli komponent dish etc.

Jeśli teraz skompilujemy aplikację i wejdziemy pod adres http://localhost:4200/ będziemy widzieć coś takiego:

Natomiast gdy w adresie dopiszemy  ‚dashboard” czyli : http://localhost:4200/dashboard to pojawi Nam się widok poprzedni:

Zatem nasz routing działa jak należy. Zauważmy, że Angular wrzucił komponent ‚dashboard’ dokładnie w miejsce gdzie jest selektor  <router-outlet></router-outlet>, co pozwala na szereg możliwości takich jak np odświeżanie tylko jednego elementu strony przy przechodzeniu na inny adres.

Wpisywanie takich adresów ręcznie, nie jest eleganckie zatem zróbmy coś co nazywa się „przekierowaniem routingu”. Załóżmy, że chcielibyśmy aby przy kompilacji aplikacji i wystartowaniu serwera na adresie http://localhost:4200/ przekierowywało nas na np dashboard, gdyż będzie to główna część programu. Dlatego w tablicy ścieżek musimy dodać jedną linijkę:

{ path: ”, pathMatch: ‚full’, redirectTo: ‚dashboard’}
Mówi nam to tyle, że gdy znajdziesz się na adresie http://localhost:4200/’ ‚ czyli pustym to jeśli nic tam nie ma (propercja pathMatch mówi nam, że ma się wszystko zgadzać, czyli w naszym przypadku ma być NIC, co obrazuje propercja path: ‚ ‚), to przekieruje nas na adres ‚dashboard’, jak mówi propercja „redirectTo”

Całość wygląda tak:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppComponent } from './app.component';
import { SharedModule } from './shared/shared.module';
import { HomeModule } from './home/home.module';
import { LoginComponent } from './login/login.component';
import { ProductComponent } from './product/product.component';
import { RouterModule } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';
import { DishComponent } from './dish/dish.component';
import { DishModule } from './dish/dish.module';

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    ProductComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    SharedModule,
    HomeModule,
    DishModule,
    RouterModule.forRoot([
      { path: '', pathMatch: 'full', redirectTo: 'dashboard'},
      { path: 'login', component: LoginComponent },
      { path: 'dashboard', component: DashboardComponent },
      { path: 'dish', component: DishComponent},
      { path: 'product', component: ProductComponent}
    ])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Przenieśmy się jeszcze do pliku sidebar.component.html i w tamtejszym menu podmieńmy linki aby wskazywały na odpowiednie komponenty. W Angularze, jeśli chcemy aby link przekierowywał nas na jakiś adres to zamiast typowego „href=” „, używamy [routerLink] =[/”]. Przeróbmy w taki sposób linki w sidebar.component.html:

<section class="sidebar" fxFlex="220px">
  <div class="main-menu-header">
    <div fxLayout='row' fxFlex="200px">
      <div fxFlex="33%">
        <button mat-mini-fab>
          <mat-icon color="primary">message</mat-icon>
        </button>
      </div>
      <div fxFlex="33%">
        <button mat-mini-fab>
          <mat-icon>favorite</mat-icon>
        </button>
      </div>
      <div fxFlex="33%">
        <button mat-mini-fab>
          <mat-icon>list</mat-icon>
        </button>
      </div>
    </div>
  </div>
  <div class="main-menu">
    <mat-list>
      <h3 mat-subheader>{{'SIDEBAR.NAVIGATION' | translate: lang}}</h3>
      <mat-list-item>
        <mat-icon mat-list-icon>dashboard</mat-icon>
        <h4 mat-line>
          <a [routerLink]="['/dashboard']">{{'SIDEBAR.DASHBOARD' | translate: lang}}</a>
        </h4>
      </mat-list-item>
      <mat-list-item>
        <mat-icon mat-list-icon>restaurant</mat-icon>
        <h4 mat-line>
          <a [routerLink]="['/dish']">{{'SIDEBAR.MY_DISHES' | translate: lang}}</a>
        </h4>
      </mat-list-item>
      <mat-list-item>
        <mat-icon mat-list-icon>fastfood</mat-icon>
        <h4 mat-line>
          <a [routerLink]="['/product']">{{'SIDEBAR.MY_PRODUCTS' | translate: lang}}</a>
        </h4>
      </mat-list-item>
      <mat-divider></mat-divider>
      <h3 mat-subheader>Notes</h3>
      <mat-list-item>
        <mat-icon mat-list-icon>note</mat-icon>
        <h4 mat-line>
          <a href="#">{{'SIDEBAR.NOTES' | translate: lang}}</a>
        </h4>
      </mat-list-item>
    </mat-list>
  </div>
</section>

Jak teraz widzimy nasz router działa i zmieniają się nam widoki. Aktualna do tego wpisu wersja aplikacji znajduje się pod adresem:

https://github.com/taaaniel/foodCalc/tree/part_7_router_part_1_end

W następnej części, omówimy szerzej pojęcie routingu, podzielimy go na moduły, napiszemy „guarda”, a także omówimy metodę router.navigate w 3 części dowiemy się czym są parametry i jak ich używać.

Jak zwykle zapraszam do komentowania i zadawani pytań.

Notatki:

Źródła

Zostaw komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *