Маршрутизация в Angular 5

Маршрутизация в Angular 5

От автора: сегодня мы рассмотрим маршрутизацию в Angular 5. Эта статья из серии – создание простого приложения на Angular 5.

Третья часть — Angular 5 — создание нового компонента.

В последней статье мы создали компонент pokemon-list, который показывает ненумерованный список имен покемонов. Задача этой статьи – кликнуть на имя покемона и перейти к роуту details, где будет отображаться подробная информация о выбранном покемоне. Приступим.

Создайте новый компонент pokemon-details для отображения дополнительной информации о выбранном покемоне.

ng generate component pokemon-details

Позже вернемся к этой команде. Создайте module маршрутизации

ng generate module app-routing

Будет создан AppRoutingModule. Прежде чем погрузиться в детали маршрутизации, давайте импортируем этот модуль в его компаньона AppModule, добавив в массив imports.

«Соблюдайте соглашение об именовании для модуля маршрутизации, так как это сопутствующий модуль. Добавьте приставку Routing к сопутствующему модулю. Например ThisModule >> ThisRoutingModule»

На данный момент наш модуль маршрутизации выглядит так (как и другие модули, сгенерированный через cli)

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; @NgModule({ imports: [ CommonModule ], declarations: []
})
export class AppRoutingModule { } 

До сих пор мы не разбирали типичный модуль, поэтому давайте быстро разберем 2 важных свойства метаданных декоратора @NgModule –

Imports – массив других модулей, чьи компоненты понадобятся в текущем модуле. В коде видно, что CommonModule добавлен в массив imports. CommonModule хранит все основные Angular директивы типа *ngIf, *ngFor и т.д.

declarations  — массив объявляемых классов: компоненты, директивы и пайпы.

В основном, в модуля маршрутизации никогда не будет своих компонентов и т.д., а значит, и шаблонов. Поэтому нужно избавиться от свойства declarations и ссылок CommonModule.

Модуль маршрутизации после чистки:

import { NgModule } from "@angular/core"; @NgModule({ imports: []
})
export class AppRoutingModule {} 

Импортируйте RouterModule и Routes из @angular/router.

Необходимо повторно экспортировать RouterModule в модуль маршрутизации, чтобы компоненты сопутствующего модуля имели доступ к директивам маршрутизации типа RouterLink (разбирается ниже в статье) и т.д.

Routes – массив настроек Route. Каждый Route, помимо множества других важных свойств, имеет 2 базовых свойства: path – url в браузере и component – компонент, созданный при посещении этого роута.

Route говорит приложению сопоставить path в url браузера и решить, какой component показывать. Обновленный модуль маршрутизации:

import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router"; @NgModule({ imports: [], exports: [RouterModule]
})
export class AppRoutingModule {} 

Пора добавить парочку роутов. Если сейчас запустить приложение через ng serve и открыть http://localhost:4200/, мы увидим список покемонов. Присвоим этому адресу роут /pokemons.

Для этого сперва необходимо создать массив объектов Route типа Routes, после чего добавить RouterModule.forRoot(Routes array) (если сопутствующий модуль root. Для всех других модулей маршрутизации forChild) в массив imports модуля маршрутизации.

import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { PokemonListComponent } from "../pokemon-list/pokemon-list.component"; const appRoutes: Routes = [ { path: "pokemons", component: PokemonListComponent }
];
@NgModule({ imports: [RouterModule.forRoot(appRoutes)], exports: [RouterModule]
})
export class AppRoutingModule {} 

Код говорит нашему приложению сопоставить path http://localhost:4200/pokemons и загрузить PokemonListComponent. Попробуйте открыть этот url, должен открыться список имен покемонов. Круто. Быстренько настроим другой Route на редирект на /pokemons при переходе по пустой ссылке.

import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { PokemonListComponent } from "../pokemon-list/pokemon-list.component"; const appRoutes: Routes = [ { path: "pokemons", component: PokemonListComponent }, { path: "", redirectTo: "/pokemons", pathMatch: "full" }
];
@NgModule({ imports: [RouterModule.forRoot(appRoutes)], exports: [RouterModule]
})
export class AppRoutingModule {} 

Ок, теперь http://localhost:4200/ будет перенаправлять нас на http://localhost:4200/pokemons, где, как и раньше, будет отображаться список покемонов.

В нашей маршрутизации есть одна ошибка. Компонент pokemon-list напрямую используется в шаблоне app.component.html. Теперь за создание компонента на основе роута будет ответственен маршрутизатор. Тем не менее, нам нужен плейсхолдер, чтобы знать, где рендерить компонент.

RouterOutlet директива предоставляет плейсхолдер. Обновите app.component.html строкой ниже:

<router-outlet></router-outlet>

Все работает точно так же, но за ширмой все сильно поменялось. Теперь когда находится url /pokemons, маршрутизатор берет подходящий компонент pokemon-list и вставляет его после router-outlet, в чем можно убедиться в DOM.

Создадим еще один роут для отображения pokemon-details. Нам необходимо, чтобы по клику на имя покемона происходил редирект на url /pokemon/<selected-pokemon-id> с подробной информацией о покемоне. Обновленный файл маршрутизатора:

import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { PokemonListComponent } from "../pokemon-list/pokemon-list.component";
import { PokemonDetailsComponent } from "../pokemon-details/pokemon-details.component"; const appRoutes: Routes = [ { path: "pokemons", component: PokemonListComponent }, { path: "", redirectTo: "/pokemons", pathMatch: "full" }, { path: "pokemon/:id", component: PokemonDetailsComponent }
];
@NgModule({ imports: [RouterModule.forRoot(appRoutes)], exports: [RouterModule]
})
export class AppRoutingModule {} 

:id в конце нового url path – это плейсхолдер для выбранного id покемона. PokemonDetailsComponent мы создали в шаге 1 для отображения деталей. Над ним мы будем работать далее. Но прежде добавьте click и redirect в новый роут для списка.

Обновите pokemon-list.component.html кодом ниже:

<div *ngIf="pokemonData"> <ul> <li *ngFor="let pokemon of pokemonData.pokemons"> <a routerLink="/pokemon/{{pokemon._id}}">{{pokemon.name}}</a> </li> </ul>
</div> 

Span с именем покемона обновлен на тег anchor.

Директива RouterLink обеспечивает навигацию к новому роуту. Обратите внимание, как мы передаем {{pokemon._id}} вместо :id для url path роута.

Вы получили навигацию по клику в списке покемонов. По клику на любое имя, вас должно перенаправить на похожий url: http://localhost:4200/pokemon/5a81d4a0ef0bba001172dc22

Где последняя часть – это id выбранного покемона, который можно взять из данных api. Сейчас компонент с деталями отображает хардкод текст. Обновим его так, чтобы он отображал какие-то интересные детали.

Необходимо добавить детальную информацию для выбранного id покемона. Обновите PokemonService, созданный в предыдущей статье, чтобы получать одного покемона по его id.

import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs/Observable"; @Injectable()
export class PokemonService { private url = "https://super-crud.herokuapp.com/pokemon"; constructor(private http: HttpClient) {} ... getPokemonById(id: string): Observable<any> { const url = `${this.url}/${id}`; return this.http.get<any>(url); }
} 

Используйте вышеуказанный сервис во время хука OnInit нашего PokemonDetailsComponent, как показано ниже:

import { Component, OnInit } from "@angular/core";
import { PokemonService } from "../pokemon.service";
import { ActivatedRoute } from "@angular/router"; @Component({ selector: "pokemon-details", templateUrl: "./pokemon-details.component.html", styleUrls: ["./pokemon-details.component.scss"]
})
export class PokemonDetailsComponent implements OnInit { private pokemon: any; constructor(private svc: PokemonService, private route: ActivatedRoute) {} ngOnInit() { this.getPokemonDetails(); } getPokemonDetails(): void { const id = this.route.snapshot.paramMap.get("id"); this.svc.getPokemonById(id).subscribe(data => { this.pokemon = data; }); }
} 

Помимо знакомого компонент, который мы изучили в предыдущих статьях, мы импортируем также класс ActivatedRoute из библиотеки маршрутизатора. В этом классе хранится информация о роутах. В нашем случае это id выбранного покемона (помните :id из конфиг path роута?).

Мы создали детали выбранного покемона, теперь давайте обновим шаблона представления компонента:

<div *ngIf="pokemon"> <h2>{{ pokemon.name | uppercase }} Details</h2> <img [src]="pokemon.image"> <p>Evolved from: {{pokemon.evolves_from}}</p>
</div> 

Обратите внимание на [src] в теге img. Это пример односторонней привязки свойства (не атрибут, хотя имя то же) в шаблонах Angular.

[src] – эквивалент ng-src из Angular 1.x.

| — это пайп, эквивалент filter из Angular 1.x.

Наш роут с детальной информацией готов к отображению имени выбранного покемона (в верхнем регистре), его фото и источника эволюции. Далее мы разберем подробно одну из концепций ядра Angular – вставку зависимостей (DI) и сервисы.

Автор: Manish

Источник: https://medium.com/

Редакция: Команда webformyself.