Маршрутизация
Добавьте маршрутизатор Angular и научитесь перемещаться между представлениями.
Для приложения Tour of Heroes появились новые требования:
- Добавить представление Панель управления.
- Добавить возможность перехода между представлениями Герои и Панель управления.
- При нажатии пользователем имени героя в любом представлении, перейти к представлению деталей выбранного героя.
- При нажатии пользователем глубокой ссылки в электронном письме, открыть представление деталей конкретного героя.
После завершения пользователи смогут перемещаться по приложению следующим образом:
Для получения дополнительной информации о роутере ознакомьтесь со страницей Маршрутизация и навигация.
После завершения работы над этой страницей приложение должно выглядеть так же, как в этом примере.
|
Чтобы увидеть изменения URL в адресной строке браузера в примере, откройте его снова в редакторе Plunker, нажав на значок в правом верхнем углу, а затем разверните окно предварительного просмотра, нажав на синюю кнопку 'X' в правом верхнем углу. |
|
Место, где вы остановились
Прежде чем продолжить работу над Туром героев, убедитесь, что у вас есть следующая структура.
angular-tour-of-heroes
src
app
app.component.ts
app.module.ts
hero.service.ts
hero.ts
hero-detail.component.ts
mock-heroes.ts
main.ts
index.html
styles.css
systemjs.config.js
tsconfig.json
node_modules ...
package.json
Поддерживайте компиляцию и запуск приложения
Введите следующую команду в окне терминала:
npm start
Эта команда запускает компилятор TypeScript в режиме «наблюдения», автоматически перекомпилируя код при его изменении. Команда одновременно запускает приложение в браузере и обновляет браузер при изменении кода.
Вы можете продолжать работу над Туром героев, не прерывая компиляцию или обновление браузера.
План действий
Вот план:
- Преобразуйте
AppComponentв оболочку приложения, которая обрабатывает только навигацию. - Переместите модули Героев внутри текущего
AppComponentв отдельныйHeroesComponent. - Добавьте маршрутизацию.
- Создайте новый
DashboardComponent. - Свяжите Панель инструментов с структурой навигации.
Маршрутизация — это другое название для навигации. Роутер — это механизм для навигации между представлениями.
Разделение AppComponent
Текущее приложение загружает AppComponent и сразу же отображает список героев.
Переработанное приложение должно представлять оболочку с выбором представлений (Панель инструментов и Герои) и затем переходить к одному из них по умолчанию.
AppComponent должен обрабатывать только навигацию, поэтому вы переместите отображение Героев из AppComponent в свой собственный HeroesComponent.
HeroesComponent
AppComponent уже предназначен для Героев. Вместо перемещения кода из AppComponent, переименуйте его в HeroesComponent и создайте отдельный AppComponent оболочку.
Сделайте следующее:
- Переименуйте файл app.component.ts как heroes.component.ts.
- Переименуйте класс
AppComponentкакHeroesComponent(переименуйте локально, только в этом файле). - Переименуйте селектор
my-appкакmy-heroes.
src/app/heroes.component.ts (только переименования)
@Component({
selector: 'my-heroes',
})
export class HeroesComponent implements OnInit {
}
Создание AppComponent
Новый AppComponent является оболочкой приложения. Он будет иметь ссылки на навигацию вверху и область отображения ниже для страниц, к которым пользователи переходят.
Выполните эти шаги:
- Создайте файл src/app/app.component.ts.
- Определите экспортируемый
AppComponentкласс. - Добавьте декоратор
@Componentнад классом с селекторомmy-app. - Переместите следующее из
HeroesComponentвAppComponent:-
titleсвойство класса. -
@Componentэлемент шаблона<h1>, содержащий привязку кtitle.
-
- Добавьте элемент
<my-heroes>в шаблон приложения чуть ниже заголовка, чтобы вы всё ещё видели героев. - Добавьте
HeroesComponentв массивdeclarationsAppModule, чтобы Angular распознавал теги<my-heroes>. - Добавьте
HeroServiceв массивprovidersAppModule, потому что вам это потребуется на каждой странице. - Удалите
HeroServiceиз массиваHeroesComponentprovidersпоскольку он был повышен. - Добавьте необходимые
importинструкции дляAppComponent.
Первый вариант выглядит следующим образом:
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<my-heroes></my-heroes>
`
})
export class AppComponent {
title = 'Tour of Heroes';
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroesComponent } from './heroes.component';
import { HeroService } from './hero.service';
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent,
HeroDetailComponent,
HeroesComponent
],
providers: [
HeroService
],
bootstrap: [ AppComponent ]
})
export class AppModule {
}
Приложение всё ещё работает и отображает героев.
Добавление маршрутизации
Вместо автоматического отображения герои должны отображаться после нажатия пользователем кнопки. Другими словами, пользователи должны иметь возможность переходить к списку героев.
Используйте роутер Angular для включения навигации.
Роутер Angular — это внешний, необязательный модуль Angular под названием RouterModule. Роутер — это комбинация нескольких предоставляемых сервисов (RouterModule), нескольких директив (RouterOutlet, RouterLink, RouterLinkActive) и конфигурации (Routes). Сначала вы настройте маршруты.
<base href>
Откройте index.html и убедитесь, что в верхней части раздела <head> есть элемент <base href="..."> (или скрипт, который динамически устанавливает этот элемент).
src/index.html (base-href)
<head> <base href="/">
Для получения дополнительной информации см. раздел Установите базовый href на странице Маршрутизация и навигация.
Настройка маршрутов
Создайте файл конфигурации для маршрутов приложения.
Маршруты сообщают маршрутизатору, какие представления отображать, когда пользователь щелкает по ссылке или вставляет URL в адресную строку браузера.
Определите первый маршрут как маршрут к компоненту героев.
src/app/app.module.ts (heroes route)
import { RouterModule } from '@angular/router';
RouterModule.forRoot([
{
path: 'heroes',
component: HeroesComponent
}
])
Эти Routes представляют собой массив определений маршрутов.
Это определение маршрута состоит из следующих частей:
-
Путь: маршрутизатор сопоставляет этот путь маршрута с URL в адресной строке браузера (
heroes). - Имя: официальное имя маршрута; оно должно начинаться с заглавной буквы, чтобы избежать путаницы с путём (
Heroes). -
Компонент: компонент, который маршрутизатор должен создать при переходе к этому маршруту (
HeroesComponent).
Узнайте больше о определении маршрутов с помощью
Routesна странице Маршрутизация и навигация.
Обеспечьте доступность маршрутизатора
Импортируйте RouterModule и добавьте его в массив импортов AppModule.
src/app/app.module.ts (app routing)
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroesComponent } from './heroes.component';
import { HeroService } from './hero.service';
@NgModule({
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot([
{
path: 'heroes',
component: HeroesComponent
}
])
],
declarations: [
AppComponent,
HeroDetailComponent,
HeroesComponent
],
providers: [
HeroService
],
bootstrap: [ AppComponent ]
})
export class AppModule {
}
Метод
forRoot()вызывается, потому что на корневом уровне приложения предоставляется настроенный маршрутизатор. МетодforRoot()предоставляет поставщиков и директивы службы маршрутизатора, необходимые для маршрутизации, и выполняет начальную навигацию на основе текущего URL браузера.
Выход маршрутизатора
Если вы вставите путь /heroes, в адресную строку браузера в конце URL, маршрутизатор должен сопоставить его с маршрутом heroes и отобразить HeroesComponent. Однако вам нужно указать маршрутизатору, где отобразить компонент. Для этого вы можете добавить элемент <router-outlet> в конец шаблона. RouterOutlet является одной из директивы, предоставленной RouterModule. Маршрутизатор отображает каждый компонент сразу под <router-outlet>, по мере навигации пользователей по приложению.
Ссылки маршрутизатора
Пользователи не должны вставлять URL маршрута в адресную строку. Вместо этого добавьте тег якоря в шаблон, который при нажатии инициирует навигацию к HeroesComponent.
Пересмотренный шаблон выглядит следующим образом:
src/app/app.component.ts (template-v2)
template: `
<h1>{{title}}</h1>
<a routerLink="/heroes">Heroes</a>
<router-outlet></router-outlet>
`
Обратите внимание на привязку routerLink в теге якоря. Директива RouterLink (ещё одна из RouterModule директив) привязана к строке, которая сообщает маршрутизатору, куда перейти, когда пользователь нажимает на ссылку.
Поскольку ссылка не динамическая, инструкция маршрутизации определена с однократной привязкой к пути маршрута. Вернувшись к конфигурации маршрутов, вы можете подтвердить, что '/heroes' является путём маршрута к HeroesComponent.
Узнайте больше о динамических ссылках маршрутизатора и массиве параметров ссылок в разделе Приложение: Массив параметров ссылок на странице Маршрутизация и навигация.
Обновите браузер. Браузер отображает заголовок приложения и ссылку героев, но не список героев.
Адресная строка браузера отображает
/. Путь маршрута кHeroesComponentсоставляет/heroes, а не/. Вскоре вы добавите маршрут, соответствующий пути/.
Нажмите на ссылку навигации Герои. Адресная строка обновляется до /heroes, и отображается список героев.
AppComponent теперь выглядит так:
src/app/app.component.ts (v2)
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<a routerLink="/heroes">Heroes</a>
<router-outlet></router-outlet>
`
})
export class AppComponent {
title = 'Tour of Heroes';
}
Компонент AppComponent теперь прикреплён к маршрутизатору и отображает маршрутизированные представления. По этой причине, и для отличия от других типов компонентов, этот тип компонента называется компонентом маршрутизатора.
Добавление панели управления
Маршрутизация имеет смысл только при наличии нескольких представлений. Чтобы добавить другое представление, создайте заполнитель DashboardComponent, к которому пользователи могут переходить и из которого они могут выходить.
src/app/dashboard.component.ts (v1)
import { Component } from '@angular/core';
@Component({
selector: 'my-dashboard',
template: '<h3>My Dashboard</h3>'
})
export class DashboardComponent { }
Вы сделаете этот компонент более полезным позже.
Настройка маршрута панели управления
Чтобы научить app.module.ts переходить на панель управления, импортируйте компонент панели управления и добавьте следующее определение маршрута в массив Routes определений.
src/app/app.module.ts (Dashboard route)
{
path: 'dashboard',
component: DashboardComponent
},
Также импортируйте и добавьте DashboardComponent в AppModule's declarations.
src/app/app.module.ts (dashboard)
declarations: [ AppComponent, DashboardComponent, HeroDetailComponent, HeroesComponent ],
Добавление маршрута перенаправления
При запуске приложения должно отображаться панель управления, и в адресной строке браузера должен отображаться /dashboard URL. В настоящее время браузер запускается с / в адресной строке.
Чтобы это произошло, используйте маршрут перенаправления. Добавьте следующее в массив определений маршрутов:
src/app/app.module.ts (redirect)
{
path: '',
redirectTo: '/dashboard',
pathMatch: 'full'
},
Узнайте больше о перенаправлениях в разделе Перенаправление маршрутов на странице Маршрутизация и навигация.
Добавление навигации в шаблон
Добавьте ссылку на навигацию по панели управления в шаблон, непосредственно над ссылкой Герои.
src/app/app.component.ts (template-v3)
template: `
<h1>{{title}}</h1>
<nav>
<a routerLink="/dashboard">Dashboard</a>
<a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
`
Теги
<nav>пока ничего не делают, но они будут полезны позже, когда вы будете форматировать ссылки.
В браузере перейдите в корень приложения (/) и перезагрузите его. Приложение отобразит панель управления, и вы сможете переходить между панелью управления и героями.
Добавление героев на панель управления
Чтобы сделать панель управления более интересной, вы будете отображать четырёх лучших героев одним взглядом.
Замените метаданные template на свойство templateUrl, которое указывает на новый файл шаблона.
src/app/dashboard.component.ts (metadata)
@Component({
selector: 'my-dashboard',
templateUrl: './dashboard.component.html',
})
Создайте этот файл со следующим содержимым:
src/app/dashboard.component.html
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<div *ngFor="let hero of heroes" class="col-1-4">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</div>
</div>
*ngFor используется снова для итерации по списку героев и отображения их имён. Дополнительные элементы <div> помогут при форматировании позже.
Общий доступ к HeroService
Чтобы заполнить массив компонентов heroes, вы можете повторно использовать HeroService.
Ранее вы удалили HeroService из массива providers HeroesComponent и добавили его в массив providers AppModule. Это перемещение создало экземпляр синглтона HeroService, доступный всем компонентам приложения. Angular вводит HeroService и вы можете использовать его в DashboardComponent.
Получение героев
В dashboard.component.ts добавьте следующие import утверждения.
src/app/dashboard.component.ts (imports)
import { Component, OnInit } from '@angular/core';
import { Hero } from './hero';
import { HeroService } from './hero.service';
Теперь создайте класс DashboardComponent следующим образом:
src/app/dashboard.component.ts (class)
export class DashboardComponent implements OnInit {
heroes: Hero[] = [];
constructor(private heroService: HeroService) { }
ngOnInit(): void {
this.heroService.getHeroes()
.then(heroes => this.heroes = heroes.slice(1, 5));
}
}
Этот вид логики также используется в HeroesComponent:
- Определите свойство массива
heroes. - Введите
HeroServiceв конструктор и сохраните его в приватном полеheroService. - Вызовите службу для получения героев внутри цикла жизни Angular
ngOnInit.
В этой панели управления вы указываете четырёх героев (2-й, 3-й, 4-й и 5-й) с помощью метода Array.slice.
Обновите браузер, чтобы увидеть четыре имени героев на новой панели управления.
Переход к деталям героя
Хотя подробности выбранного героя отображаются в нижней части HeroesComponent, пользователи должны иметь возможность переходить к HeroDetailComponent следующими дополнительными способами:
- С панели управления к выбранному герою.
- Из списка героев к выбранному герою.
- Из URL «глубокой ссылки», вставленного в адресную строку браузера.
Маршрутизация к деталям героя
Вы можете добавить маршрут к HeroDetailComponent в app.module.ts, где настраиваются другие маршруты.
Новый маршрут необычен тем, что вы должны указать маршрутизатору, какого героя отобразить. Вам не нужно было сообщать об этом HeroesComponent или DashboardComponent.
В настоящее время родительский HeroesComponent устанавливает свойство компонента hero в объект героя с привязкой такого типа:
<hero-detail [hero]="selectedHero"></hero-detail>
Но эта привязка не будет работать ни в одном из сценариев маршрутизации.
Параметризованный маршрут
Вы можете добавить id героя в URL. При переходе к герою, чей id равен 11, вы могли бы увидеть URL такого типа:
/detail/11
Часть /detail/ URL постоянна. Цифровая часть id изменяется от героя к герою. Вам нужно представить переменную часть маршрута с помощью параметра (или токена), который представляет id героя.
Настройка маршрута с параметром
Используйте следующее определение маршрута.
src/app/app.module.ts (hero detail)
{
path: 'detail/:id',
component: HeroDetailComponent
},
Двоеточие (:) в пути указывает, что :id — это плейсхолдер для конкретного героя id, когда вы переходите к HeroDetailComponent.
Вы закончили с маршрутами приложения.
Вы не добавили ссылку 'Hero Detail' в шаблон, потому что пользователи не нажимают на ссылку навигации, чтобы просмотреть конкретного героя; они нажимают на имя героя, независимо от того, отображается ли оно на панели управления или в списке героев.
Вам не нужно добавлять щелчки по героям, пока HeroDetailComponent не будет пересмотрена и готова к навигации.
Пересмотр HeroDetailComponent
Вот как выглядит HeroDetailComponent сейчас:
src/app/hero-detail.component.ts (current)
import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
selector: 'hero-detail',
template: `
<div *ngIf="hero">
<h2>{{hero.name}} details!</h2>
<div>
<label>id: </label>{{hero.id}}
</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
</div>
`
})
export class HeroDetailComponent {
@Input() hero: Hero;
}
Шаблон не изменится. Имена героев будут отображаться так же. Основные изменения касаются того, как вы получаете имена героев.
Вы больше не будете получать героя в свойстве родительского компонента с привязкой. Новый HeroDetailComponent должен получать параметр id из params Observable в ActivatedRoute службе и использовать HeroService для получения героя с этим id.
Добавьте следующие импорты:
src/app/hero-detail.component (added-imports)
// Keep the Input import for now, you'll remove it later:
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { Location } from '@angular/common';
import { HeroService } from './hero.service';
Введите ActivatedRoute, HeroService, и Location службы в конструктор, сохранив их значения в закрытых полях:
src/app/hero-detail.component.ts (constructor)
constructor(
private heroService: HeroService,
private route: ActivatedRoute,
private location: Location
) {}
Импортируйте оператор switchMap для последующего использования с параметрами маршрута Observable.
src/app/hero-detail.component.ts (switchMap import)
import 'rxjs/add/operator/switchMap';
Укажите, что класс должен реализовывать интерфейс OnInit.
src/app/hero-detail.component.ts (implement)
export class HeroDetailComponent implements OnInit {
Внутри жизненного цикла ngOnInit, используйте params Observable, чтобы извлечь значение параметра id из ActivatedRoute службы и используйте HeroService для получения героя с этим id.
src/app/hero-detail.component.ts (ngOnInit)
ngOnInit(): void {
this.route.params
.switchMap((params: Params) => this.heroService.getHero(+params['id']))
.subscribe(hero => this.hero = hero);
}
Оператор switchMap сопоставляет id в Observable параметрах маршрута с новым Observable, результатом метода HeroService.getHero().
Если пользователь повторно переходит к этому компоненту, пока запрос getHero всё ещё обрабатывается, switchMap отменяет старый запрос и затем снова вызывает HeroService.getHero().
Герой id — это число. Параметры маршрута всегда являются строками. Поэтому значение параметра маршрута преобразуется в число с помощью JavaScript-оператора (+).
Нужно ли отписываться?
Как описано в разделе ActivatedRoute: the one-stop-shop for route information страницы Маршрутизация и навигация,
Routerуправляет предоставляемыми Observable и локализует подписки. Подписки удаляются при уничтожении компонента, предотвращая утечки памяти, поэтому вам не нужно отписываться от маршрутаparamsObservable.
Добавление HeroService.getHero
В предыдущем фрагменте кода у HeroService нет метода getHero(). Чтобы исправить эту проблему, откройте HeroService и добавьте метод getHero(), который фильтрует список героев из getHeroes по id.
src/app/hero.service.ts (getHero)
getHero(id: number): Promise<Hero> {
return this.getHeroes()
.then(heroes => heroes.find(hero => hero.id === id));
}
Возврат назад
Пользователи могут несколькими способами перейти к HeroDetailComponent.
Чтобы перейти куда-либо ещё, пользователи могут щелкнуть одной из двух ссылок в AppComponent или нажать кнопку «Назад» браузера. Теперь добавьте третий вариант — метод goBack(), который перемещает пользователя на один шаг назад в стеке истории браузера, используя ранее введённую службу Location.
src/app/hero-detail.component.ts (goBack)
goBack(): void {
this.location.back();
}
Переход слишком далеко может вывести пользователей из приложения. В реальном приложении можно предотвратить эту проблему с помощью инструмента CanDeactivate. Узнайте больше на странице CanDeactivate.
Вы свяжете этот метод с привязкой события к кнопке «Назад», которую вы добавите в шаблон компонента.
<button (click)="goBack()">Back</button>
Переместите шаблон в отдельный файл под названием hero-detail.component.html:
src/app/hero-detail.component.html
<div *ngIf="hero">
<h2>{{hero.name}} details!</h2>
<div>
<label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name" />
</div>
<button (click)="goBack()">Back</button>
</div>
Обновите метаданные компонента с помощью templateUrl на файл шаблона, который вы только что создали.
src/app/hero-detail.component.ts (metadata)
@Component({
selector: 'hero-detail',
templateUrl: './hero-detail.component.html',
})
Обновите браузер и посмотрите результаты.
Выбор героя на панели управления
Когда пользователь выбирает героя на панели управления, приложение должно перейти к HeroDetailComponent для просмотра и редактирования выбранного героя.
Хотя герои на панели управления представлены в виде блоков кнопок, они должны вести себя как ссылки. При наведении курсора на блок героя целевой URL должен отображаться в строке состояния браузера, а пользователь должен иметь возможность скопировать ссылку или открыть представление героя в новой вкладке.
Для достижения этого эффекта откройте dashboard.component.html и замените повторяющиеся теги <div *ngFor...> на теги <a>. Измените открывающий тег <a> на следующий:
src/app/dashboard.component.html (repeated <a> tag)
<a *ngFor="let hero of heroes" [routerLink]="['/detail', hero.id]" class="col-1-4">
Обратите внимание на привязку [routerLink]. Как описано в разделе Ссылки маршрутизатора этой страницы, навигация верхнего уровня в шаблоне AppComponent содержит ссылки маршрутизатора, заданные для фиксированных путей целевых маршрутов, "/dashboard" и "/heroes".
На этот раз вы привязываетесь к выражению, содержащему массив параметров ссылки. Массив содержит два элемента: путь целевого маршрута и параметр маршрута, установленный в значение текущего поля id.
Два элемента массива соответствуют пути и :id маркерам в определении параметризованного маршрута подробной информации о герое, который вы добавили в app.module.ts ранее:
src/app/app.module.ts (hero detail)
{
path: 'detail/:id',
component: HeroDetailComponent
},
Обновите браузер и выберите героя на панели управления; приложение переходит к странице подробностей этого героя.
Рефакторинг маршрутов в модуль маршрутизации
Почти 20 строк AppModule посвящены настройке четырёх маршрутов. Большинство приложений имеют намного больше маршрутов, и они добавляют службы защиты для предотвращения нежелательной или несанкционированной навигации. (Дополнительную информацию о службах защиты см. в разделе Route Guards страницы Маршрутизация и навигация). Маршрутизация может быстро доминировать в этом модуле и затенять его основное назначение, заключающееся в установлении ключевых фактов о всего приложении для компилятора Angular.
Полезно рефакторить конфигурацию маршрутизации в собственный класс. Текущий RouterModule.forRoot() создаёт Angular ModuleWithProviders, класс, посвящённый маршрутизации, должен быть модулем маршрутизации. Дополнительную информацию см. в разделе Этап №2: Модуль маршрутизации страницы Маршрутизация и навигация.
По соглашению имя модуля маршрутизации содержит слово "Routing" и соответствует имени модуля, в котором объявлены перенаправляемые компоненты.
Создайте файл app-routing.module.ts как брата/сестры app.module.ts. Заполните его следующим содержимым, извлечённым из класса AppModule:
src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DashboardComponent } from './dashboard.component';
import { HeroesComponent } from './heroes.component';
import { HeroDetailComponent } from './hero-detail.component';
const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'detail/:id', component: HeroDetailComponent },
{ path: 'heroes', component: HeroesComponent }
];
@NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
Следующие пункты типичны для модулей маршрутизации:
- Модуль маршрутизации подтягивает маршруты в переменную. Переменная проясняет шаблон модуля маршрутизации на случай, если вы экспортируете модуль в будущем.
- Модуль маршрутизации добавляет
RouterModule.forRoot(routes)кimports. - Модуль маршрутизации добавляет
RouterModuleкexports, чтобы компоненты в сопутствующем модуле имели доступ к декларациям маршрутизатора, таким какRouterLinkиRouterOutlet. - Нет
declarations. Декларации являются ответственностью сопутствующего модуля. - Если у вас есть службы защиты, модуль маршрутизации добавляет модуль
providers(в этом примере их нет).
Обновление AppModule
Удалите конфигурацию маршрутизации из AppModule и импортируйте AppRoutingModule. Используйте инструкцию ES import и добавьте её в список NgModule.imports.
Вот пересмотренный AppModule, по сравнению со своим состоянием до рефакторинга:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { DashboardComponent } from './dashboard.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroesComponent } from './heroes.component';
import { HeroService } from './hero.service';
import { AppRoutingModule } from './app-routing.module';
@NgModule({
imports: [
BrowserModule,
FormsModule,
AppRoutingModule
],
declarations: [
AppComponent,
DashboardComponent,
HeroDetailComponent,
HeroesComponent
],
providers: [ HeroService ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { HeroDetailComponent } from './hero-detail.component';
import { DashboardComponent } from './dashboard.component';
import { HeroesComponent } from './heroes.component';
import { HeroService } from './hero.service';
@NgModule({
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot([
{
path: '',
redirectTo: '/dashboard',
pathMatch: 'full'
},
{
path: 'dashboard',
component: DashboardComponent
},
{
path: 'detail/:id',
component: HeroDetailComponent
},
{
path: 'heroes',
component: HeroesComponent
}
])
],
declarations: [
AppComponent,
DashboardComponent,
HeroDetailComponent,
HeroesComponent
],
providers: [
HeroService
],
bootstrap: [ AppComponent ]
})
export class AppModule {
}
Пересмотренный и упрощённый AppModule сосредоточен на определении ключевых частей приложения.
Выбор героя в HeroesComponent
В HeroesComponent, текущий шаблон демонстрирует стиль «мастер/детали» со списком героев вверху и деталями выбранного героя внизу.
src/app/heroes.component.ts (current template)
template: `
<h1>{{title}}</h1>
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<hero-detail [hero]="selectedHero"></hero-detail>
`,
Удалите <h1> вверху.
Удалите последнюю строку шаблона с тегами <hero-detail>.
Вы больше не будете показывать весь HeroDetailComponent здесь. Вместо этого вы будете отображать подробности героя на отдельной странице и переходить к нему, как и в панели управления.
Однако при выборе пользователя героя из списка он не перейдёт на страницу с деталями. Вместо этого он увидит мини-детали на этой странице и должен будет нажать кнопку, чтобы перейти к полной странице с деталями.
Добавление мини-деталей
Добавьте следующий фрагмент HTML в нижнюю часть шаблона, где раньше находился <hero-detail>:
src/app/heroes.component.ts (mini-detail)
<div *ngIf="selectedHero">
<h2>
{{selectedHero.name | uppercase}} is my hero
</h2>
<button (click)="gotoDetail()">View Details</button>
</div>
После нажатия на героя пользователи должны увидеть что-то вроде этого под списком героев:
Форматирование с прописной буквой
Имя героя отображается заглавными буквами из-за uppercase символа, который включён в привязку интерполяции, сразу после оператора «|» (pipe).
{{selectedHero.name | uppercase}} is my hero
Трубы — хороший способ форматирования строк, денежных сумм, дат и других данных для отображения. Angular поставляется с несколькими трубами, и вы можете создать свои собственные.
Дополнительную информацию о трубах можно найти на странице Трубы.
Вынесение содержимого за пределы файла компонента
Вам всё ещё нужно обновить класс компонента, чтобы поддерживать навигацию к HeroDetailComponent при нажатии пользователем кнопки «Просмотреть детали».
Файл компонента большой. Сложно найти логику компонента среди HTML и CSS.
Прежде чем вносить какие-либо изменения, перенесите шаблон и стили в свои собственные файлы.
Сначала перенесите содержимое шаблона из heroes.component.ts в новый файл heroes.component.html. Не копируйте обратные кавычки. Что касается heroes.component.ts, к этому вы вернётесь через минуту. Затем перенесите содержимое стилей в новый файл heroes.component.css.
Два новых файла должны выглядеть так:
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<div *ngIf="selectedHero">
<h2>
{{selectedHero.name | uppercase}} is my hero
</h2>
<button (click)="gotoDetail()">View Details</button>
</div>
.selected {
background-color: #CFD8DC !important;
color: white;
}
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroes li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.heroes li.selected:hover {
background-color: #BBD8DC !important;
color: white;
}
.heroes .text {
position: relative;
top: -3px;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
button {
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
cursor: hand;
}
button:hover {
background-color: #cfd8dc;
}
Теперь, вернувшись к метаданным компонента для heroes.component.ts, удалите template и styles, заменив их на templateUrl и styleUrls соответственно. Установите их свойства для ссылки на новые файлы.
src/app/heroes.component.ts (пересмотренные метаданные)
@Component({
selector: 'my-heroes',
templateUrl: './heroes.component.html',
styleUrls: [ './heroes.component.css' ]
})
Свойство
styleUrls— это массив имён файлов стилей (с путями). Вы можете перечислить несколько файлов стилей из разных расположений, если вам это необходимо.
Обновление класса HeroesComponent
HeroesComponent переходит к HeroesDetailComponent в ответ на нажатие кнопки. Событие нажатия кнопки связано с методом gotoDetail(), который навигацирует *императивно*, указывая маршрутизатору, куда перейти.
Для этого изменения необходимо внести следующие изменения в класс компонента:
- Импортировать
routerиз библиотеки маршрутизации Angular. - В конструкторе ввести
routerвместе сHeroService. - Реализовать
gotoDetail()вызовом методаrouter.navigate().
src/app/heroes.component.ts (gotoDetail)
gotoDetail(): void {
this.router.navigate(['/detail', this.selectedHero.id]);
}
Обратите внимание, что вы передаёте массив параметров ссылки из двух элементов — путь и параметр маршрута — методу router.navigate(), точно так же, как вы сделали в привязке [routerLink] в DashboardComponent. Вот пересмотренный класс %%%CODE_BLOCK_267%%:
src/app/heroes.component.ts (класс)
export class HeroesComponent implements OnInit {
heroes: Hero[];
selectedHero: Hero;
constructor(
private router: Router,
private heroService: HeroService) { }
getHeroes(): void {
this.heroService.getHeroes().then(heroes => this.heroes = heroes);
}
ngOnInit(): void {
this.getHeroes();
}
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
gotoDetail(): void {
this.router.navigate(['/detail', this.selectedHero.id]);
}
}
Обновите браузер и начните нажимать. Пользователи могут перемещаться по приложению: от панели инструментов к деталям героев и обратно, от списка героев к мини-деталям, к деталям героя и обратно к списку героев.
Вы выполнили все требования к навигации, которые потребовались в этом разделе.
Стиль приложения
Приложение функционально, но ему нужен стиль. Герои на панели инструментов должны отображаться в строке прямоугольников. Для этой цели вы получили около 60 строк CSS, включая некоторые простые медиазапросы для адаптивного дизайна.
Как вы теперь знаете, добавление CSS в метаданные компонента затруднит нахождение логики компонента. Вместо этого измените CSS в отдельном файле *.css.
Добавьте файл dashboard.component.css в папку app и укажите на него в свойстве массива styleUrls метаданных компонента, как это было сделано ранее:
src/app/dashboard.component.ts (styleUrls)
styleUrls: [ './dashboard.component.css' ]
Добавление стильных деталей героя
Вам также предоставлены CSS-стили специально для HeroDetailComponent.
Добавьте файл hero-detail.component.css в папку app и укажите на него в массиве styleUrls, как вы делали для DashboardComponent. Также в hero-detail.component.ts, удалите свойство hero декоратора @Input и его импорт.
Вот содержимое файлов CSS компонента.
label {
display: inline-block;
width: 3em;
margin: .5em 0;
color: #607D8B;
font-weight: bold;
}
input {
height: 2em;
font-size: 1em;
padding-left: .4em;
}
button {
margin-top: 20px;
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer; cursor: hand;
}
button:hover {
background-color: #cfd8dc;
}
button:disabled {
background-color: #eee;
color: #ccc;
cursor: auto;
}
[class*='col-'] {
float: left;
padding-right: 20px;
padding-bottom: 20px;
}
[class*='col-']:last-of-type {
padding-right: 0;
}
a {
text-decoration: none;
}
*, *:after, *:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
h3 {
text-align: center; margin-bottom: 0;
}
h4 {
position: relative;
}
.grid {
margin: 0;
}
.col-1-4 {
width: 25%;
}
.module {
padding: 20px;
text-align: center;
color: #eee;
max-height: 120px;
min-width: 120px;
background-color: #607D8B;
border-radius: 2px;
}
.module:hover {
background-color: #EEE;
cursor: pointer;
color: #607d8b;
}
.grid-pad {
padding: 10px 0;
}
.grid-pad > [class*='col-']:last-of-type {
padding-right: 20px;
}
@media (max-width: 600px) {
.module {
font-size: 10px;
max-height: 75px; }
}
@media (max-width: 1024px) {
.grid {
margin: 0;
}
.module {
min-width: 60px;
}
}
Стиль ссылок навигации
Предоставленный CSS делает ссылки навигации в AppComponent более похожими на настраиваемые кнопки. Вы поместите эти ссылки в теги <nav>.
Добавьте файл app.component.css в папку app со следующим содержимым.
src/app/app.component.css (стили навигации)
h1 {
font-size: 1.2em;
color: #999;
margin-bottom: 0;
}
h2 {
font-size: 2em;
margin-top: 0;
padding-top: 0;
}
nav a {
padding: 5px 10px;
text-decoration: none;
margin-top: 10px;
display: inline-block;
background-color: #eee;
border-radius: 4px;
}
nav a:visited, a:link {
color: #607D8B;
}
nav a:hover {
color: #039be5;
background-color: #CFD8DC;
}
nav a.active {
color: #039be5;
}
Директива routerLinkActive
Angular Router предоставляет директиву
routerLinkActive, которую можно использовать для добавления класса к HTML-элементу навигации, маршрут которого соответствует активному маршруту. Всё, что вам нужно сделать, — это определить стиль для него.src/app/app.component.ts (активные ссылки маршрута)
template: ` <h1>{{title}}</h1> <nav> <a routerLink="/dashboard" routerLinkActive="active">Dashboard</a> <a routerLink="/heroes" routerLinkActive="active">Heroes</a> </nav> <router-outlet></router-outlet> `,
Добавьте свойство styleUrls, которое ссылается на этот файл CSS следующим образом:
src/app/app.component.ts (styleUrls)
styleUrls: ['./app.component.css'],
Глобальные стили приложения
При добавлении стилей в компонент вы сохраняете всё необходимое для компонента — HTML, CSS и код — в одном удобном месте. Легко собрать всё это вместе и повторно использовать компонент где-либо ещё.
Вы также можете создавать стили на уровне приложения вне любого компонента.
Дизайнеры предоставили некоторые базовые стили для применения к элементам всего приложения. Они соответствуют полному набору основных стилей, которые вы установили ранее во время настройки. Вот выдержка:
src/styles.css (выдержка)
/* Master Styles */
h1 {
color: #369;
font-family: Arial, Helvetica, sans-serif;
font-size: 250%;
}
h2, h3 {
color: #444;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
body {
margin: 2em;
}
body, input[text], button {
color: #888;
font-family: Cambria, Georgia;
}
/* . . . */
/* everywhere else */
* {
font-family: Arial, Helvetica, sans-serif;
}
Создайте файл styles.css. Убедитесь, что файл содержит предоставленные здесь основные стили. Также измените index.html, чтобы указать на этот стилизованный документ.
src/index.html (ссылка)
<link rel="stylesheet" href="styles.css">
Теперь посмотрите на приложение. Панель инструментов, герои и ссылки навигации стилизованы.
END_OF_DOCUMENT_MARKERСтруктура приложения и код
Изучите пример исходного кода на странице с интерактивным примером. Убедитесь, что у вас есть следующая структура:
angular-tour-of-heroes
src
app
app.component.css
app.component.ts
app.module.ts
app-routing.module.ts
dashboard.component.css
dashboard.component.html
dashboard.component.ts
hero.service.ts
hero.ts
hero-detail.component.css
hero-detail.component.html
hero-detail.component.ts
heroes.component.css
heroes.component.html
heroes.component.ts
mock-heroes.ts
main.ts
index.html
styles.css
systemjs.config.js
tsconfig.json
node_modules ...
package.json
Путь, который вы прошли
Вот что вы добились на этой странице:
- Вы добавили маршрутизатор Angular для навигации между разными компонентами.
- Вы узнали, как создавать ссылки маршрутизатора для представления элементов навигационного меню.
- Вы использовали параметры ссылок маршрутизатора для перехода к данным о выбранном герое.
- Вы разделили
HeroServiceмежду несколькими компонентами. - Вы перенесли HTML и CSS из файла компонента в свои собственные файлы.
- Вы добавили
uppercaseдля форматирования данных.
Ваше приложение должно выглядеть так же, как и в этом интерактивном примере.
Путь вперед
У вас есть значительная часть фундамента для создания приложения. Вам всё ещё недостаёт ключевого элемента: доступа к удалённым данным.
На следующей странице вы замените тестовые данные данными, полученными с сервера с помощью http.
Следующий шаг
HTTP
© 2010–2017 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://v2.angular.io/docs/ts/latest/tutorial/toh-pt5.html