Определение маршрутов¶
Маршрутизация позволяет сопоставлять запросы к приложению с определенными ресурсами внутри приложения.
Ключевым для работы маршрутизации является модуль RouterModule
, который располагается в пакете @angular/router
. Поэтому при работе с маршрутизацией этот пакет должен быть указан в списке зависимостей в файле package.json
:
{ "name": "helloapp", "version": "1.0.0", "description": "First Angular 7 Project", "author": "Eugene Popov <metanit.com>", "scripts": { "dev": "webpack-dev-server --hot --open", "build": "webpack" }, "dependencies": { "@angular/router": "~7.0.0" // остальные пакеты }, "devDependencies": { // остальные пакеты } }
Для определения маршрутов возьмем базовую структуру приложения:
Для работы с маршрутизацией в первую очередь стоит определить базовый адрес приложения. Для этого возьмем веб-страницу index.html
и добавим в секцию <head>
элемент <base>
:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <base href="/" /> <title>Hello Angular 7</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" /> </head> <body> <my-app>Загрузка...</my-app> <script src="public/polyfills.js"></script> <script src="public/app.js"></script> </body> </html>
В качестве базового адреса будет рассматриваться корень приложения.
Каждый маршрут сопоставляется с определенным компонентом. Поэтому добавим в проект ряд компонентов. Так, добавим в папку src/app
новый файл home.component.ts
:
import { Component } from '@angular/core' @Component({ selector: 'home-app', template: ` <h3>Главная</h3> ` }) export class HomeComponent {}
Этот простенький компонент выводит обычный заголовок.
Далее добавим в папку src/app
новый файл about.component.ts
:
import { Component } from '@angular/core' @Component({ selector: 'about-app', template: ` <h3>О сайте</h3> ` }) export class AboutComponent {}
И также добавим еще один файл not-found.component.ts
:
import { Component } from '@angular/core' @Component({ selector: 'not-found-app', template: ` <h3>Страница не найдена</h3> ` }) export class NotFoundComponent {}
RouterModule и добавление маршрутов¶
Итак, кроме главного компонента AppComponent
в проекте определено еще три компонента, каждый из которых просто выводит некоторый заголовок. Для каждого из этих компонентов мы можем определить свой маршрут. Для этого изменим код модуля AppModule
:
import { NgModule } from '@angular/core' import { BrowserModule } from '@angular/platform-browser' import { Routes, RouterModule } from '@angular/router' import { AppComponent } from './app.component' import { AboutComponent } from './about.component' import { HomeComponent } from './home.component' import { NotFoundComponent } from './not-found.component' // определение маршрутов const appRoutes: Routes = [ { path: '', component: HomeComponent }, { path: 'about', component: AboutComponent }, { path: '**', component: NotFoundComponent } ] @NgModule({ imports: [BrowserModule, RouterModule.forRoot(appRoutes)], declarations: [ AppComponent, HomeComponent, AboutComponent, NotFoundComponent ], bootstrap: [AppComponent] }) export class AppModule {}
Во-первых, здесь импортируются модуль маршрутизации RouterModule
и класс Routes
, представляющий коллекцию маршрутов:
import { Routes, RouterModule } from '@angular/router'
Далее определяется сам набор маршрутов:
const appRoutes: Routes = [ { path: '', component: HomeComponent }, { path: 'about', component: AboutComponent }, { path: '**', component: NotFoundComponent } ]
Здесь определено три маршрута, каждый из которых будет обрабатываться отдельным компонентом. Для указания маршрута применяется параметр path
. Например, путь about
будет представлять запрос типа http://localhost:3000/about
и будет обрабатываться классом AboutComponent
.
Если запрос не содержит никаких сегментов, например, просто имя домена http://localhost:3000/
, то такой запрос будет сопоставляться с путем "" (пустая строка) и будет обрабатываться компонентом HomeComponent
.
Если приложение получит запрос, который не подходит ни под один из выше определенных маршрутов, то он будет сопоставляться с шаблоном **
, где две звездочки представляют любой путь.
Чтобы применить маршруты, они передаются в метод RouterModule.forRoot(appRoutes)
:
imports: [ BrowserModule, RouterModule.forRoot(appRoutes)],
Метод RouterModule.forRoot()
возвращает модуль, который содержит сконфигурированный сервис Router
. Когда приложение загружается, Router
выполняет начальную навигацию по текущему URL, который стоит в адресной строке браузера.
RouterOutlet¶
Мы определили три разных компонента для разных маршрутов, однако в качестве главного компонента выступает AppComponent
. Этот компонент выступает в качестве контейнера для остальных компонентов, которые будут обслуживать запросы к приложению.
Но чтобы можно было внедрить в AppComponent
тот компонент, который обрабатывает запрос, необходимо использовать элемент RouterOutlet
. Для этого изменим код AppComponent
:
import { Component } from '@angular/core' @Component({ selector: 'my-app', template: ` <div> <h1>Маршрутизация в Angular 6</h1> <router-outlet></router-outlet> </div> ` }) export class AppComponent {}
На место элемента <router-outlet>
будет рендериться компонент, выбранный для обработки запроса.
Если при разработке применяется webpack, нужно определить в файле webpack.config.js
следующую секцию:
devServer: { historyApiFallback: true, }
То есть файл webpack.config.js
может выглядеть следующим образом:
var path = require('path') var webpack = require('webpack') var UglifyJSPlugin = require('uglifyjs-webpack-plugin') // плагин минимизации module.exports = { entry: { polyfills: './src/polyfills.ts', app: './src/main.ts' }, output: { path: path.resolve(__dirname, './public'), // путь к каталогу выходных файлов - папка public publicPath: '/public/', filename: '[name].js' // название создаваемого файла }, devServer: { historyApiFallback: true }, resolve: { extensions: ['.ts', '.js'] }, module: { rules: [ //загрузчик для ts { test: /\.ts$/, // определяем тип файлов use: [ { loader: 'awesome-typescript-loader', options: { configFileName: path.resolve(__dirname, 'tsconfig.json') } }, 'angular2-template-loader' ] } ] }, plugins: [ new webpack.ContextReplacementPlugin( /angular(\\|\/)core/, path.resolve(__dirname, 'src'), // каталог с исходными файлами {} // карта маршрутов ), new UglifyJSPlugin() ] }
Запустим приложение. По умолчанию приложение запускается без сегментов, поэтому запрос обрабатывает HomeComponent
:
Перейдем по пути localhost:xxxx/about
:
При переходе к любому другому адресу сработает компонент NotFoundComponent
:
URL Matching и порядок маршрутов¶
Когда будет выполняться переход по определенному пути, например, /about
, система маршрутизации сопоставляет последовательно URL запроса с параметрами path
у каждого маршрута. Данный процесс называется url matching
. В частности, система маршрутизации сопоставит url /about
с маршрутом { path: 'about', component: AboutComponent}
. И компонент AboutComponent
будет выбран для обработки запроса по пути /about
.
Но при определении маршрутов следует учитывать их порядок. Вполне возможно, что под определенный запрос будет соответствовать сразу несколько маршрутов. В этом случае запрос будет обрабатываться первым из них. Другие же маршруты не будут учитываться. Например, если мы изменим порядок маршрутов:
const appRoutes: Routes = [ { path: '**', component: NotFoundComponent }, { path: '', component: HomeComponent }, { path: 'about', component: AboutComponent } ]
То в этом случае запрос /about
будет обрабатываться первым маршрутом, поскольку он соответствует запросу /about
(путь **
соответствует любому набору символов). Поэтому маршрут
{ path: '**', component: NotFoundComponent }
лучше определить последним - для всех тех запросов, которые не будут соответствовать ни одному из выше определенных маршрутов.
Переадресация¶
Вполне возможно, что по какому-то маршруту мы захотим сделать переадресацию по другому пути. Например, в случае, если нужного маршрута для запроса не найдено, мы можем переадресовать на главную страницу:
const appRoutes: Routes = [ { path: '', component: HomeComponent }, { path: 'about', component: AboutComponent }, { path: '**', redirectTo: '/' } ]
Для переадресации указываем параметр redirectTo
. Его значение представляет путь переадресации. В данном случае слеш указывает на первый маршрут или на главную страницу.
Также мы можем задать критерий соответствия строки запроса маршруту с помощью параметра pathMatch
:
const appRoutes: Routes = [ { path: '', component: HomeComponent }, { path: 'about', component: AboutComponent }, { path: 'contact', redirectTo: '/about', pathMatch: 'full' }, { path: '**', redirectTo: '/' } ]
Значение pathMatch:'full'
указывает, что запрошенный адрес должен полностью соответствовать маршруту, то есть должно быть полное соответствие. Например, запрос /contact
полностью соотвествует маршруту { path: 'contact', redirectTo: '/about', pathMatch:'full'}
, поэтому будет выполняться переадресация на адрес /about
.
А запрос /contact/5
не будет соответствовать этому маршруту, так как после contact
идут другие сегменты.