Это четвертая часть руководства SitePoint по Angular 2+ о том, как создать приложение CRUD с помощью Angular CLI . В этой статье мы познакомимся с Angular Router и узнаем, как он может обновлять наше приложение при изменении URL браузера и наоборот. Мы также узнаем, как мы можем обновить наше приложение для разрешения данных из нашего внутреннего API с помощью маршрутизатора.
В первой части мы узнали, как запустить наше приложение Todo и развернуть его на страницах GitHub. Это работало просто отлично, но, к сожалению, все приложение было собрано в один компонент.
Во второй части мы рассмотрели более модульную архитектуру компонентов и узнали, как разбить этот отдельный компонент на структурированное дерево более мелких компонентов, которые проще для понимания, повторного использования и обслуживания.
В третьей части мы обновили наше приложение для связи с серверной частью REST API, используя RxJS и HTTP-сервис Angular.
- Часть 0 — Ultimate Angular CLI Справочное руководство
- Часть 1. Подготовка и запуск нашей первой версии приложения Todo
- Часть 2. Создание отдельных компонентов для отображения списка задач и одной задачи
- Часть 3. Обновление сервиса Todo для связи с REST API
- Часть 4. Использование углового маршрутизатора для разрешения данных
- Часть 5. Добавление аутентификации для защиты частного контента.
- Часть 6 — Как обновить Angular Projects до последней версии.
Не волнуйся! Вам не нужно следовать первой, второй или третьей части этого урока, чтобы четыре имели смысл. Вы можете просто взять копию нашего репо , получить код из третьей части и использовать его в качестве отправной точки. Это объясняется более подробно ниже.
Вверх и работает
Убедитесь, что у вас установлена последняя версия Angular CLI. Если вы этого не сделаете, вы можете установить его с помощью следующей команды:
npm install -g @angular/cli@latest
Если вам нужно удалить предыдущую версию Angular CLI, вы можете сделать это:
npm uninstall -g @angular/cli angular-cli npm cache clean npm install -g @angular/cli@latest
После этого вам понадобится копия кода из третьей части. Это доступно на GitHub . Каждая статья в этой серии имеет соответствующий тег в репозитории, чтобы вы могли переключаться между различными состояниями приложения.
Код, который мы закончили в третьей части и с которого мы начнем в этой статье, помечен как часть-3 . Код, которым мы заканчиваем эту статью, помечен как часть-4 .
Вы можете думать о тегах как псевдоним определенного идентификатора коммита. Вы можете переключаться между ними, используя git checkout
. Вы можете прочитать больше об этом здесь .
Итак, чтобы начать работу (установлена последняя версия Angular CLI), мы должны сделать следующее:
git clone [email protected]:sitepoint-editors/angular-todo-app.git cd angular-todo-app git checkout part-3 npm install ng serve
Затем посетите http: // localhost: 4200 / . Если все хорошо, вы должны увидеть работающее приложение Todo.
Краткий обзор
Вот как выглядела наша архитектура приложения в конце третьей части:
В этой статье мы будем:
- узнать, почему приложение может нуждаться в маршрутизации
- узнать, что такое роутер JavaScript
- узнать, что такое Angular Router, как он работает и что он может сделать для вас
- настройте Angular Router и настройте маршруты для нашего приложения
- создать распознаватель для извлечения задач из нашего REST API
- обновите наше приложение, чтобы получить задачи, используя наш новый распознаватель.
К концу этой статьи вы поймете:
- когда и почему вашему приложению может потребоваться маршрутизация
- разница между маршрутизацией на сервере и маршрутизацией в браузере
- что такое Angular Router и что он может сделать для вашего приложения
- как настроить Angular Router
- как настроить маршруты для вашего приложения
- как сказать Angular Router, где размещать компоненты в DOM
- как изящно обрабатывать неизвестные URL
- что такое резольвер и для чего его можно использовать
- Как использовать распознаватель для разрешения данных с помощью Angular Router.
Итак, начнем!
Почему пойдут?
В своем текущем состоянии наше веб-приложение не учитывает URL-адрес браузера.
Мы обращаемся к нашему приложению через один URL-адрес, такой как http://localhost:4200
и наше приложение не знает никаких других URL-адресов, таких как http://localhost:4200/todos
.
Большинство веб-приложений должны поддерживать разные URL-адреса, чтобы перемещать пользователей на разные страницы приложения. Вот где приходит маршрутизатор.
На традиционных веб-сайтах маршрутизация обрабатывается маршрутизатором на сервере:
- пользователь щелкает ссылку в браузере, что приводит к изменению URL
- браузер отправляет HTTP-запрос на сервер
- сервер читает URL из HTTP-запроса и генерирует соответствующий HTTP-ответ
- сервер отправляет HTTP-ответ в браузер.
В современных веб-приложениях JavaScript маршрутизация часто обрабатывается маршрутизатором JavaScript в браузере.
Что такое роутер JavaScript?
По сути, JavaScript-маршрутизатор делает две вещи:
- обновить состояние веб-приложения при изменении URL браузера
- обновите URL-адрес браузера при изменении состояния веб-приложения.
JavaScript-роутеры позволяют нам разрабатывать одностраничные приложения (SPA).
SPA — это веб-приложение, которое обеспечивает взаимодействие с пользователем, аналогичное настольному приложению. В SPA вся связь с серверной частью происходит за кулисами.
Когда пользователь перемещается с одной страницы на другую, страница обновляется динамически без перезагрузки, даже если URL-адрес изменяется.
Существует много различных реализаций маршрутизатора JavaScript.
Некоторые из них специально написаны для определенной среды JavaScript, такой как Angular , Ember , React , Vue.js и Aurelia , и т. Д. Другие реализации созданы для общих целей и не привязаны к конкретной среде.
Что такое угловой маршрутизатор?
Angular Router — это официальная библиотека угловой маршрутизации, созданная и поддерживаемая Angular Core Team.
Это реализация маршрутизатора JavaScript, разработанная для работы с Angular и упакованная как @angular/router
.
Прежде всего, Angular Router берет на себя обязанности JavaScript-маршрутизатора:
- активирует все необходимые компоненты Angular для создания страницы, когда пользователь переходит на определенный URL
- это позволяет пользователям перемещаться с одной страницы на другую без перезагрузки страницы
- он обновляет историю браузера, чтобы пользователь мог использовать кнопки « назад» и « вперед» при переходе назад и вперед между страницами.
Кроме того, Angular Router позволяет нам:
- перенаправить URL на другой URL
- разрешить данные перед отображением страницы
- запускать скрипты, когда страница активирована или деактивирована
- ленивая загрузка частей нашего приложения.
В этой статье мы узнаем, как настроить и настроить Angular Router, как перенаправить URL-адрес и как использовать Angular Router для разрешения задач из нашего внутреннего API.
В следующей статье мы добавим аутентификацию в наше приложение и используем маршрутизатор, чтобы обеспечить доступ к некоторым страницам только при входе пользователя в систему.
Как работает угловой маршрутизатор
Прежде чем мы углубимся в код, важно понять, как работает Angular Router, и какую терминологию он вводит.
Когда пользователь переходит на страницу, Angular Router выполняет следующие шаги по порядку:
- он читает URL браузера, к которому хочет перейти пользователь
- применяется перенаправление URL (если оно определено)
- он выясняет, какое состояние маршрутизатора соответствует URL
- он запускает охранники, которые определены в состоянии маршрутизатора
- он разрешает необходимые данные для состояния маршрутизатора
- активирует угловые компоненты для отображения страницы
- он управляет навигацией и повторяет описанные выше шаги при запросе новой страницы.
Для выполнения своих задач Angular Router вводит следующие термины и понятия:
- сервис маршрутизатора : глобальный сервис Angular Router в нашем приложении
- конфигурация маршрутизатора : определение всех возможных состояний маршрутизатора, в котором может находиться наше приложение
- Состояние маршрутизатора : состояние маршрутизатора в определенный момент времени, выраженное в виде дерева активированных снимков маршрута.
- активированный снимок маршрута : предоставляет доступ к URL, параметрам и данным для узла состояния маршрутизатора
- guard : скрипт, который запускается, когда маршрут загружен, активирован или деактивирован
- resolver : скрипт, который выбирает данные перед активацией запрошенной страницы.
- розетка маршрутизатора : местоположение в DOM, где Angular Router может разместить активированные компоненты.
Не волнуйтесь, если терминология звучит подавляюще. Вы привыкнете к этим терминам, поскольку мы постепенно будем их использовать в этой серии, и по мере того, как вы приобретете больше опыта в работе с Angular Router.
Приложение Angular, которое использует Angular Router, имеет только один экземпляр службы маршрутизатора: это одноэлементное приложение. Когда бы и где бы вы ни внедрили службу Router
в свое приложение, вы получите доступ к одному и тому же экземпляру службы Angular Router.
Для более глубокого ознакомления с процессом угловой маршрутизации обязательно ознакомьтесь с 7-шаговым процессом маршрутизации угловой навигации .
Включение маршрутизации
Чтобы включить маршрутизацию в нашем приложении Angular, нам нужно сделать три вещи:
- создать конфигурацию маршрутизации, которая определяет возможные состояния для нашего приложения
- импортировать конфигурацию маршрутизации в наше приложение
- добавьте розетку маршрутизатора, чтобы сообщить Angular Router, куда поместить активированные компоненты в DOM.
Итак, начнем с создания конфигурации маршрутизации.
Создание конфигурации маршрутизации
Чтобы создать нашу конфигурацию маршрутизации, нам нужен список URL-адресов, которые мы хотим, чтобы наше приложение поддерживало.
В настоящее время наше приложение очень простое и имеет только одну страницу, которая показывает список задач:
-
/
: показать список задач
который будет показывать список задач в качестве домашней страницы нашего приложения.
Однако, когда пользователь делает закладку /
в своем браузере, чтобы просмотреть свой список задач, и мы меняем содержимое нашей домашней страницы (что мы сделаем в части 5 этой серии), его закладка больше не будет отображать список задач.
Итак, давайте дадим нашему списку задач собственный URL и перенаправим на него нашу домашнюю страницу:
-
/
: перенаправить на/todos
-
/todos
: показать список задач.
Это дает нам два преимущества:
- когда пользователи создают закладки для страницы задач, их браузер будет
/todos
закладки/todos
задачи вместо/
, что будет работать должным образом, даже если мы изменим содержимое домашней страницы - Теперь мы можем легко изменить нашу домашнюю страницу, перенаправив ее на любой URL, который нам нравится, что удобно, если вам необходимо регулярно менять содержимое домашней страницы.
Официальное руководство по стилю Angular рекомендует хранить конфигурацию маршрутизации для модуля Angular в файле с именем файла, оканчивающимся на -routing.module.ts
который экспортирует отдельный модуль Angular с именем, оканчивающимся на RoutingModule
.
Наш текущий модуль называется AppModule
, поэтому мы создаем файл src/app/app-routing.module.ts
и экспортируем нашу конфигурацию маршрутизации как угловой модуль с именем AppRoutingModule
:
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AppComponent } from './app.component'; const routes: Routes = [ { path: '', redirectTo: 'todos', pathMatch: 'full' }, { path: 'todos', component: AppComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], providers: [] }) export class AppRoutingModule { }
Сначала мы импортируем RouterModule
и Routes
из @angular/router
:
import { RouterModule, Routes } from '@angular/router';
Далее мы определяем переменную routes
типа Routes
и назначаем ей нашу конфигурацию маршрутизатора:
const routes: Routes = [ { path: '', redirectTo: 'todos', pathMatch: 'full' }, { path: 'todos', component: AppComponent } ];
Тип Routes
является необязательным и позволяет IDE с поддержкой TypeScript или компилятором TypeScript удобно проверять конфигурацию вашего маршрута во время разработки.
Конфигурация маршрутизатора представляет все возможные состояния маршрутизатора, в которых может находиться наше приложение.
Это дерево маршрутов, определенное как массив JavaScript, где каждый маршрут может иметь следующие свойства:
- путь : строка, путь к URL
- pathMatch : строка, как соответствовать URL
- компонент : ссылка на класс, компонент, который нужно активировать при активации этого маршрута
- redirectTo : строка, URL-адрес для перенаправления при активации этого маршрута
- данные : статические данные для назначения маршруту
- resol : динамические данные для разрешения и объединения с данными после разрешения
- дети : детские маршруты.
Наше приложение простое и содержит только два родственных маршрута, но более крупное приложение может иметь конфигурацию маршрутизатора с дочерними маршрутами, такими как:
const routes: Routes = [ { path: '', redirectTo: 'todos', pathMatch: 'full' }, { path: 'todos', children: [ { path: '', component: 'TodosPageComponent' }, { path: ':id', component: 'TodoPageComponent' } ] } ];
Здесь todos
имеет два дочерних маршрута и :id
— это параметр маршрута, позволяющий маршрутизатору распознавать следующие URL:
-
/
: домашняя страница, перенаправление на/todos
-
/todos
: активироватьTodosPageComponent
и показать список задач -
/todos/1
: активироватьTodoPageComponent
и установить значение параметра:id
в1
-
/todos/2
: активироватьTodoPageComponent
и установить значение параметра:id
равным2
.
Обратите внимание, как мы указываем pathMatch: 'full'
при определении перенаправления.
Angular Router имеет две подходящие стратегии:
- префикс : по умолчанию, соответствует, когда URL начинается со значения
path
- full : соответствует, когда URL-адрес равен значению
path
.
Мы можем создать следующий маршрут:
// no pathMatch specified, so Angular Router applies // the default `prefix` pathMatch { path: '', redirectTo: 'todos' }
В этом случае Angular Router применяет стратегию сопоставления пути prefix
по умолчанию, и каждый URL-адрес перенаправляется в todos
поскольку каждый URL-адрес начинается с пустой строки ''
указанной в path
.
Мы только хотим, чтобы наша домашняя страница была перенаправлена в todos
, поэтому мы добавляем pathMatch: 'full'
чтобы убедиться, что сопоставляется только тот URL, который равен пустой строке ''
:
{ path: '', redirectTo: 'todos', pathMatch: 'full' }
Чтобы узнать больше о различных параметрах конфигурации маршрутизации, ознакомьтесь с официальной документацией Angular по маршрутизации и навигации .
Наконец, мы создаем и экспортируем угловой модуль AppRoutingModule
:
@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], providers: [] }) export class AppRoutingModule { }
Существует два способа создания модуля маршрутизации:
-
RouterModule.forRoot(routes)
: создает модуль маршрутизации, который включает директивы маршрутизатора, конфигурацию маршрута и службу маршрутизатора -
RouterModule.forChild(routes)
: создает модуль маршрутизации, который включает директивы маршрутизатора, конфигурацию маршрута, но не службу маршрутизатора.
Метод RouterModule.forChild()
необходим, когда ваше приложение имеет несколько модулей маршрутизации.
Помните, что служба роутера заботится о синхронизации между состоянием нашего приложения и URL браузера. Создание нескольких служб маршрутизатора, взаимодействующих с одним и тем же URL-адресом браузера, может привести к проблемам, поэтому важно, чтобы в нашем приложении был только один экземпляр службы маршрутизатора, независимо от того, сколько модулей маршрутизации мы импортируем в наше приложение.
Когда мы импортируем модуль маршрутизации, созданный с помощью RouterModule.forRoot()
, Angular создаст экземпляр службы маршрутизатора. Когда мы импортируем модуль маршрутизации, созданный с помощью RouterModule.forChild()
, Angular не будет создавать экземпляр службы маршрутизатора.
Поэтому мы можем использовать RouterModule.forRoot()
только один раз и использовать RouterModule.forChild()
несколько раз для дополнительных модулей маршрутизации.
Поскольку наше приложение имеет только один модуль маршрутизации, мы используем RouterModule.forRoot()
:
imports: [RouterModule.forRoot(routes)]
Кроме того, мы также указываем RouterModule
в свойстве RouterModule
:
exports: [RouterModule]
Это гарантирует, что нам не придется явно импортировать RouterModule
снова в AppModule
когда AppModule
импортирует AppRoutingModule
.
Теперь, когда у нас есть наш AppRoutingModule
, нам нужно импортировать его в наш AppModule
чтобы включить его.
Импорт конфигурации маршрутизации
Чтобы импортировать нашу конфигурацию маршрутизации в наше приложение, мы должны импортировать AppRoutingModule
в наш основной AppModule
.
Давайте откроем src/app/app.module.ts
и добавим AppRoutingModule
в массив imports
в метаданных @NgModule
:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; import { AppComponent } from './app.component'; import { TodoListComponent } from './todo-list/todo-list.component'; import { TodoListFooterComponent } from './todo-list-footer/todo-list-footer.component'; import { TodoListHeaderComponent } from './todo-list-header/todo-list-header.component'; import { TodoDataService } from './todo-data.service'; import { TodoListItemComponent } from './todo-list-item/todo-list-item.component'; import { ApiService } from './api.service'; import { AppRoutingModule } from './app-routing.module'; @NgModule({ declarations: [ AppComponent, TodoListComponent, TodoListFooterComponent, TodoListHeaderComponent, TodoListItemComponent ], imports: [ AppRoutingModule, BrowserModule, FormsModule, HttpModule ], providers: [TodoDataService, ApiService], bootstrap: [AppComponent] }) export class AppModule { }
Поскольку AppRoutingModule
имеет RoutingModule
указанное в свойстве exports
, Angular будет импортировать RoutingModule
автоматически, когда мы импортируем AppRoutingModule
, поэтому нам не нужно явно импортировать RouterModule
снова (хотя это не причинит никакого вреда).
Прежде чем мы сможем опробовать наши изменения в браузере, нам нужно выполнить третий и последний шаг.
Добавление выхода роутера
Хотя наше приложение теперь имеет конфигурацию маршрутизации, нам все равно нужно сообщить Angular Router, где оно может разместить созданные экземпляры компонентов в DOM.
Когда наше приложение загружается, Angular создает экземпляр AppComponent
поскольку AppComponent
указан в свойстве bootstrap
AppModule
:
@NgModule({ // ... bootstrap: [AppComponent] }) export class AppModule { }
Чтобы сообщить Angular Router, где он может размещать компоненты, мы должны добавить элемент <router-outlet></router-outlet>
в HTML-шаблон AppComponent
.
Элемент <router-outlet></router-outlet>
сообщает Angular Router, где он может создавать экземпляры компонентов в DOM.
Если вы знакомы с AngularJS 1.x router и UI-Router , вы можете рассмотреть <router-outlet></router-outlet>
как угловую альтернативу ng-view
и ui-view
.
Без элемента <router-outlet></router-outlet>
Angular Router не знал бы, где разместить компоненты, и AppComponent
только собственный HTML- AppComponent
.
AppComponent
настоящее время отображает список задач.
Но вместо того, чтобы позволить AppComponent
отображать список AppComponent
, мы теперь хотим, чтобы AppComponent
содержал <router-outlet></router-outlet>
и AppComponent
Angular Router создать другой компонент внутри AppComponent
для отображения списка задач.
Для этого сгенерируем новый компонент TodosComponent
с помощью Angular CLI:
$ ng generate component Todos
Давайте также переместим весь HTML из src/app/app.component.html
в src/app/todos/todos.component.html
:
<!-- src/app/todos/todos.component.html --> <section class="todoapp"> <app-todo-list-header (add)="onAddTodo($event)" ></app-todo-list-header> <app-todo-list [todos]="todos" (toggleComplete)="onToggleTodoComplete($event)" (remove)="onRemoveTodo($event)" ></app-todo-list> <app-todo-list-footer [todos]="todos" ></app-todo-list-footer> </section>
Давайте также переместим всю логику из src/app/app.component.ts
в src/app/todos/todos.component.ts
:
/* src/app/todos/todos.component.ts */ import { Component, OnInit } from '@angular/core'; import { TodoDataService } from '../todo-data.service'; import { Todo } from '../todo'; @Component({ selector: 'app-todos', templateUrl: './todos.component.html', styleUrls: ['./todos.component.css'], providers: [TodoDataService] }) export class TodosComponent implements OnInit { todos: Todo[] = []; constructor( private todoDataService: TodoDataService ) { } public ngOnInit() { this.todoDataService .getAllTodos() .subscribe( (todos) => { this.todos = todos; } ); } onAddTodo(todo) { this.todoDataService .addTodo(todo) .subscribe( (newTodo) => { this.todos = this.todos.concat(newTodo); } ); } onToggleTodoComplete(todo) { this.todoDataService .toggleTodoComplete(todo) .subscribe( (updatedTodo) => { todo = updatedTodo; } ); } onRemoveTodo(todo) { this.todoDataService .deleteTodoById(todo.id) .subscribe( (_) => { this.todos = this.todos.filter((t) => t.id !== todo.id); } ); } }
Теперь мы можем заменить AppComponent
в src/app/app.component.html
:
<router-outlet></router-outlet>
Мы также можем удалить весь устаревший код из AppComponent
в src/app/app.component.ts
:
import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent { }
Наконец, мы обновляем наш маршрут todos
в src/app/app-routing.module.ts
чтобы создать экземпляр TodosComponent
вместо AppComponent
:
const routes: Routes = [ { path: '', redirectTo: 'todos', pathMatch: 'full' }, { path: 'todos', component: TodosComponent } ];
Теперь, когда наше приложение загружается, Angular создает экземпляр AppComponent
и находит <router-outlet></router-outlet>
где Angular Router может создавать экземпляры и активировать компоненты.
Давайте попробуем наши изменения в браузере.
Запустите свой сервер разработки и свой внутренний API, выполнив:
$ ng serve $ npm run json-server
Затем перейдите в браузере по http://localhost:4200
.
Angular Router считывает конфигурацию маршрутизатора и автоматически перенаправляет наш браузер на http://localhost:4200/todos
.
Если вы осмотрите элементы на странице, то увидите, что TodosComponent
отображается не внутри <router-outlet></router-outlet>
, а прямо рядом с ним:
<app-root> <!-- Angular Router finds router outlet --> <router-outlet></router-outlet> <!-- and places the component right next to it, NOT inside it --> <app-todos></app-todos> </app-root>
В нашем приложении включена маршрутизация. Потрясающие!
Добавление группового маршрута
Когда вы перейдете в браузер по http://localhost:4200/unmatched-url
и откроете инструменты разработчика вашего браузера, вы заметите, что Angular Router регистрирует следующую ошибку на консоли:
Error: Cannot match any routes. URL Segment: 'unmatched-url'
Изящно обрабатывать несопоставленные URL-адреса нам нужно сделать две вещи:
- Создайте
PageNotFoundComponent
(вы можете назвать его по-другому, если хотите), чтобы отобразить дружеское сообщение о том, что запрошенная страница не может быть найдена - Скажите Angular Router, чтобы он отображал
PageNotFoundComponent
когда ни один маршрут не соответствует запрошенному URL.
Давайте начнем с генерации PageNotFoundComponent
с использованием Angular CLI:
$ ng generate component PageNotFound
Затем отредактируйте его шаблон в src/app/page-not-found/page-not-found.component.html
:
<p>We are sorry, the requested page could not be found.</p>
Далее мы добавляем маршрутный символ, используя **
в качестве пути:
const routes: Routes = [ { path: '', redirectTo: 'todos', pathMatch: 'full' }, { path: 'todos', component: AppComponent }, { path: '**', component: PageNotFoundComponent } ];
**
соответствует любому URL, включая дочерние пути.
Теперь, если вы перейдете в браузер по http://localhost:4200/unmatched-url
PageNotFoundComponent
http://localhost:4200/unmatched-url
, PageNotFoundComponent
.
Обратите внимание, что подстановочный маршрут должен быть последним маршрутом в нашей конфигурации маршрутизации, чтобы он работал должным образом.
Когда Angular Router сопоставляет URL-адрес запроса с конфигурацией маршрутизатора, он прекращает обработку, как только находит первое совпадение.
Так что, если мы изменим порядок маршрутов так:
const routes: Routes = [ { path: '', redirectTo: 'todos', pathMatch: 'full' }, { path: '**', component: PageNotFoundComponent }, { path: 'todos', component: AppComponent } ];
тогда todos
никогда не будут достигнуты, и PageNotFoundComponent
будет отображаться, потому что PageNotFoundComponent
будет PageNotFoundComponent
маршрутный символ.
Мы уже многое сделали, поэтому давайте быстро подведем итоги того, что мы уже сделали:
- мы настроили угловой маршрутизатор
- мы создали конфигурацию маршрутизации для нашего приложения
- мы реорганизовали
AppComponent
вTodosComponent
- мы добавили
<router-outlet></router-outlet>
вAppComponent
- мы добавили маршрутный символ для изящной обработки несоответствующих URL.
Далее мы создадим распознаватель для извлечения существующих задач из нашего внутреннего API с использованием Angular Router.
Разрешение данных с использованием углового маршрутизатора
В третьей части этой серии мы уже узнали, как извлекать данные из нашего внутреннего API с помощью службы Angular HTTP.
В настоящее время, когда мы перемещаем наш браузер по URL todos
, происходит следующее:
- Angular Router соответствует URL
todos
- Angular Router активирует
TodosComponent
- Angular Router размещает
TodosComponent
рядом с<router-outlet></router-outlet>
в DOM -
TodosComponent
отображается в браузере с пустым массивом задач -
ngOnInit
из API в обработчикеTodosComponent
-
TodosComponent
обновляется в браузере с помощьюTodosComponent
из API.
Если загрузка задач на шаге 5 занимает три секунды, пользователю будет представлен пустой список задач на три секунды, прежде чем на шаге 6 отобразятся фактические задачи.
Если бы у TodosComponent
был следующий HTML в его шаблоне:
<div *ngIf="!todos.length"> You currently do not have any todos yet. </div>
затем пользователь будет видеть это сообщение в течение трех секунд, прежде чем отобразятся фактические задачи, что может полностью ввести пользователя в заблуждение и привести к тому, что пользователь уйдет, прежде чем поступят фактические данные.
Мы могли бы добавить загрузчик в TodosComponent
который показывает TodosComponent
во время загрузки данных, но иногда мы можем не иметь контроля над фактическим компонентом, например, когда мы используем сторонний компонент.
Чтобы исправить это нежелательное поведение, нам нужно следующее:
- Angular Router соответствует URL
todos
- Angular Router извлекает задачи из API
- Angular Router активирует
TodosComponent
- Angular Router размещает
TodosComponent
рядом с<router-outlet></router-outlet>
в DOM -
TodosComponent
отображается в браузере сTodosComponent
полученными из API.
Здесь TodosComponent
не отображается, пока не будут доступны данные из нашего API-интерфейса.
Это именно то, что решатель может сделать для нас.
Чтобы Angular Router разрешил задачи до того, как он активирует TodosComponent
, мы должны сделать две вещи:
- создать
TodosResolver
который выбираетTodosResolver
из API - Скажите Angular Router использовать
TodosResolver
для извлеченияTodosResolver
при активацииTodosComponent
на маршрутеTodosComponent
.
Прикрепляя распознаватель к маршруту todos
мы просим Angular Router сначала разрешить данные, прежде TodosComponent
активировать TodosComponent
.
Итак, давайте создадим распознаватель для извлечения наших задач.
Создание TodosResolver
В Angular CLI нет команды для создания распознавателя, поэтому давайте создадим новый файл src/todos.resolver.ts
вручную и добавим следующий код:
import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { Todo } from './todo'; import { TodoDataService } from './todo-data.service'; @Injectable() export class TodosResolver implements Resolve<Observable<Todo[]>> { constructor( private todoDataService: TodoDataService ) { } public resolve( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<Todo[]> { return this.todoDataService.getAllTodos(); } }
Мы определяем преобразователь как класс, который реализует интерфейс Resolve
.
Интерфейс Resolve
является необязательным, но позволяет нашей IDE TypeScript или компилятору гарантировать, что мы правильно реализуем класс, требуя от нас реализации метода resolve()
.
Когда Angular Router необходимо разрешить данные с помощью распознавателя, он вызывает метод resolver resolve()
и ожидает, что метод resol resolve()
вернет значение, обещание или наблюдаемое.
Если метод resolve()
возвращает обещание или наблюдаемый Angular Router будет ждать завершения обещания или наблюдаемого, прежде чем активировать компонент маршрута.
При вызове метода resol resolve()
Angular Router удобно передает активированный снимок маршрута и снимок состояния маршрутизатора, чтобы предоставить нам доступ к данным (таким как параметры маршрута или параметры запроса), которые нам могут понадобиться для разрешения данных.
Код для TodosResolver
очень TodosResolver
, потому что у нас уже есть TodoDataService
который обрабатывает все взаимодействия с нашим API-интерфейсом.
Мы TodoDataService
в конструктор и используем его getAllTodos()
для извлечения всех getAllTodos()
методе getAllTodos()
.
Метод разрешения возвращает наблюдаемую информацию типа Todo[]
, поэтому Angular Router будет ожидать завершения наблюдаемой, прежде чем активируется компонент маршрута.
Теперь, когда у нас есть наш распознаватель, давайте настроим Angular Router для его использования.
Разрешение задач через роутер
Чтобы Angular Router использовал распознаватель, мы должны прикрепить его к маршруту в нашей конфигурации маршрута.
Давайте откроем src/app-routing.module.ts
и добавим наш TodosResolver
в маршрут todos
:
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { PageNotFoundComponent } from './page-not-found/page-not-found.component'; import { TodosComponent } from './todos/todos.component'; import { TodosResolver } from './todos.resolver'; const routes: Routes = [ { path: '', redirectTo: 'todos', pathMatch: 'full' }, { path: 'todos', component: TodosComponent, resolve: { todos: TodosResolver } }, { path: '**', component: PageNotFoundComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], providers: [ TodosResolver ] }) export class AppRoutingModule { }
Мы импортируем TodosResolver
:
import { TodosResolver } from './todos.resolver';
Также добавьте его в качестве распознавателя для маршрута todos
:
{ path: 'todos', component: TodosComponent, resolve: { todos: TodosResolver } }
Это указывает Angular Router разрешать данные с помощью TodosResolver
и назначать возвращаемое значение преобразователя в качестве todos
в данных маршрута.
Доступ к данным маршрута можно получить из ActivatedRoute
или ActivatedRouteSnapshot
, которые мы увидим в следующем разделе.
Вы можете добавить статические данные непосредственно к данным маршрута, используя свойство данных маршрута:
{ path: 'todos', component: TodosComponent, data: { title: 'Example of static route data' } }
Вы также можете добавить динамические данные с помощью распознавателя, указанного в свойстве resolve
маршрута:
resolve: { path: 'todos', component: TodosComponent, resolve: { todos: TodosResolver } }
Вы также можете сделать оба одновременно:
resolve: { path: 'todos', component: TodosComponent, data: { title: 'Example of static route data' } resolve: { todos: TodosResolver } }
Как только преобразователи из свойства разрешения разрешаются, их значения объединяются со статическими данными из свойства data
и все данные становятся доступными в качестве данных маршрута.
Angular Router использует внедрение зависимостей Angular для доступа к распознавателям, поэтому мы должны убедиться, что регистрируем TodosResolver
в системе внедрения зависимостей Angular, добавив его в свойство providers
в метаданных @NgModule
:
@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], providers: [ TodosResolver ] }) export class AppRoutingModule { }
Когда вы перейдете в браузер по http://localhost:4200
, Angular Router теперь:
- перенаправляет URL с
/
на/todos
- видит, что для маршрута
todos
определенTodosResolver
в свойствеTodosResolver
- запускает метод
TodosResolver
resolve()
изTodosResolver
, ждет результата и присваивает результатtodos
в данных маршрута - активирует
TodosComponent
.
Если вы откроете вкладку сети ваших инструментов разработчика, то увидите, что задачи теперь дважды выбираются из API. Один раз по Angular Router и один раз по обработчику TodosComponent
в TodosComponent
.
Таким образом, Angular Router уже извлекает задачи из API, но TodosComponent
прежнему использует собственную внутреннюю логику для загрузки задач.
В следующем разделе мы обновим TodosComponent
для использования данных, разрешенных Angular Router.
Использование разрешенных данных
Давайте откроем app/src/todos/todos.component.ts
.
В настоящее время обработчик ngOnInit()
извлекает задачи непосредственно из API:
public ngOnInit() { this.todoDataService .getAllTodos() .subscribe( (todos) => { this.todos = todos; } ); }
Теперь, когда Angular Router извлекает TodosResolver
с помощью TodosResolver
, мы хотим извлечь TodosComponent
в TodosComponent
из данных маршрута вместо API.
Чтобы получить доступ к данным маршрута, мы должны импортировать ActivatedRoute
из @angular/router
:
import { ActivatedRoute } from '@angular/router';
и использовать внедрение зависимостей Angular, чтобы получить дескриптор активированного маршрута:
constructor( private todoDataService: TodoDataService, private route: ActivatedRoute ) { }
Наконец, мы обновляем обработчик ngOnInit()
чтобы получить задачи из данных маршрута вместо API:
public ngOnInit() { this.route.data .map((data) => data['todos']) .subscribe( (todos) => { this.todos = todos; } ); }
ActivatedRoute
представляет данные маршрута как наблюдаемые, поэтому наш код практически не изменяется.
Мы заменяем this.todoDataService.getAllTodos()
на this.route.data.map((data) => data['todos'])
и весь остальной код остается неизменным.
Если вы перейдете в браузер по localhost:4200
и откроете вкладку сети, вы больше не увидите двух HTTP-запросов, извлекающих задачи из API.
Миссия выполнена! Мы успешно интегрировали Angular Router в наше приложение!
Прежде чем закончить, давайте запустим наши модульные тесты:
ng serve
Один модульный тест не пройден:
Executed 11 of 11 (1 FAILED) TodosComponent should create FAILED 'app-todo-list-header' is not a known element
Когда TodosComponent
стенд не знает о TodoListHeaderComponent
и, таким образом, Angular жалуется, что не знает элемент app-todo-list-header
.
Чтобы исправить эту ошибку, давайте откроем app/src/todos/todos.component.spec.ts
и добавим NO_ERRORS_SCHEMA
в опции TestBed
:
beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [TodosComponent], schemas: [ NO_ERRORS_SCHEMA ] }) .compileComponents(); }));
Теперь Карма показывает еще одну ошибку:
Executed 11 of 11 (1 FAILED) TodosComponent should create FAILED No provider for ApiService!
Давайте добавим необходимых провайдеров к опциям тестовой кровати:
beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [TodosComponent], schemas: [ NO_ERRORS_SCHEMA ], providers: [ TodoDataService, { provide: ApiService, useClass: ApiMockService } ], }) .compileComponents(); }));
Это снова вызывает еще одну ошибку:
Executed 11 of 11 (1 FAILED) TodosComponent should create FAILED No provider for ActivatedRoute!!
Давайте добавим еще одного провайдера для ActivatedRoute
к опциям тестового стенда:
beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [TodosComponent], schemas: [ NO_ERRORS_SCHEMA ], providers: [ TodoDataService, { provide: ApiService, useClass: ApiMockService }, { provide: ActivatedRoute, useValue: { data: Observable.of({ todos: [] }) } } ], }) .compileComponents(); }));
Мы назначаем провайдеру для ActivatedRoute
фиктивный объект, который содержит наблюдаемое свойство данных, чтобы предоставить тестовое значение для todos
.
Сейчас юнит-тесты успешно проходят:
Executed 11 of 11 SUCCESS
Потрясающе! Чтобы развернуть наше приложение в производственной среде, теперь мы можем запустить:
ng build --aot --environment prod
Мы загружаем сгенерированный каталог dist
на наш хостинг-сервер. Насколько это сладко?
Мы многое рассмотрели в этой статье, поэтому давайте вспомним то, что мы узнали.
Резюме
В первой статье мы узнали, как:
- инициализировать наше приложение Todo с помощью Angular CLI
- создать класс
Todo
для представления отдельных задач - создать сервис
TodoDataService
для создания, обновления и удаления задач - используйте компонент
AppComponent
для отображения пользовательского интерфейса - разверните наше приложение на страницах GitHub
Во второй статье мы реорганизовали AppComponent
чтобы делегировать большую часть его работы:
-
TodoListComponent
для отображения списка задач -
TodoListItemComponent
для отображения одного todo -
TodoListHeaderComponent
для создания новой задачи -
TodoListFooterComponent
чтобы показать, сколькоTodoListFooterComponent
.
В третьей статье мы узнали, как:
- создать макет REST API
- сохранить URL API в качестве переменной среды
- создать
ApiService
для связи с REST API - обновите
TodoDataService
чтобы использовать новыйApiService
- обновить
AppComponent
для обработки асинхронных вызовов API - создайте
ApiMockService
чтобы избежать реальных HTTP-вызовов при выполнении модульных тестов.
В этой четвертой статье мы узнали:
- почему приложение может нуждаться в маршрутизации
- что такое роутер JavaScript
- что такое Angular Router, как он работает и что он может сделать для вас
- как настроить Angular Router и настроить маршруты для нашего приложения
- как сказать Angular Router, где размещать компоненты в DOM
- как изящно обрабатывать неизвестные URL
- как использовать распознаватель, чтобы позволить Angular Router разрешать данные.
Весь код из этой статьи доступен на GitHub .
В пятой части мы внедрим аутентификацию, чтобы предотвратить несанкционированный доступ к нашему приложению.
Так что следите за обновлениями и, как всегда, не стесняйтесь оставлять свои мысли и вопросы в комментариях!