Spec-Zone.ru › Angular 2

Маршрутизация

Добавьте маршрутизатор 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 в массив declarations AppModule, чтобы Angular распознавал теги <my-heroes>.
  • Добавьте HeroService в массив providers AppModule, потому что вам это потребуется на каждой странице.
  • Удалите HeroService из массива HeroesComponent providers поскольку он был повышен.
  • Добавьте необходимые import инструкции для AppComponent.

Первый вариант выглядит следующим образом:

src/app/app.component.ts (v1)
import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <my-heroes></my-heroes>
  `
})
export class AppComponent {
  title = 'Tour of Heroes';
}
src/app/app.module.ts (v1)
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 необходим

Для получения дополнительной информации см. раздел Установите базовый 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 и локализует подписки. Подписки удаляются при уничтожении компонента, предотвращая утечки памяти, поэтому вам не нужно отписываться от маршрута params Observable.

Добавление 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, по сравнению со своим состоянием до рефакторинга:

src/app/app.module.ts (after)
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 { }
src/app/app.module.ts (before)
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.

Два новых файла должны выглядеть так:

src/app/heroes.component.html
<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>
src/app/heroes.component.css
.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(), который навигацирует *императивно*, указывая маршрутизатору, куда перейти.

Для этого изменения необходимо внести следующие изменения в класс компонента:

  1. Импортировать router из библиотеки маршрутизации Angular.
  2. В конструкторе ввести router вместе с HeroService.
  3. Реализовать 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 компонента.

src/app/hero-detail.component.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;
}
src/app/dashboard.component.css
[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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API