Маршрутизация¶
В Angular маршрутизация представляет собой переход от одного представления (шаблона) к другому в зависимости от заданного URL. Причем навигация может осуществляться и внутри представления.
Навигация в Angular приложениях происходит без перезагрузки страницы.
Ключевая роль в формировании URL принадлежит тегу <base>
, указывающему путь к приложению относительно расположения файла index.html
. Если index.html
располагается в директории клиентского приложения, то тег должен быть записан следующим образом.
<base href="/" />
Если бы клиентское приложение находилось в директории example
, а index.html
на одном уровне иерархии с ней, то было бы так:
<base href="/example" />
За организацию маршрутизации в Angular отвечает модуль RouterModule
библиотеки @angular/router
.
URL организуются в специальные модули и определяются для каждого отдельного модуля приложения.
Рассмотрим создание модуля AppRoutingModule
.
const routes: Routes = [
{ path: 'login', component: LoginRouteComponent },
{
path: 'home',
component: HomeRouteComponent,
children: [
{ path: 'profile', component: ProfileRouteComponent },
],
},
{
path: 'contacts',
redirectTo: '/home',
pathMatch: 'full',
children: [
{
path: 'director',
component: DirectorContactsRouteComponent,
},
],
},
{ path: '**', component: LoginRouteComponent },
]
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
При определении маршрута можно указать ряд свойств:
path
— наименование маршрута;component
— компонент для отображения при переходе на URL, совпадающий сpath
;children
— одно из дополнительных свойств, объединяющее в себе группу маршрутов относительно текущего;data
— дополнительные данные, например, значение хлебных крошек;redirectTo
— перенаправляет на указанный URL при попадании на маршрут, указанный вpath
;pathMatch
— используется совместно сredirectTo
.
Обратите внимание, что компоненты-маршруты должны быть импортированы в модуль Angular маршрутизации.
Родительский маршрут не обязательно должен определять свойство component, он может просто объединять в себе логически связанные маршруты. Например, в примере выше contacts
— родитель для страниц с контактами.
Разберем подробно назначение свойства pathMatch
. У нас имеется маршрут contacts
, прямой переход на который вызвал бы ошибку, если бы не было определено свойство redirectTo
, поскольку для contacts
не определен компонент.
Теперь представим, что redirectTo
указан, а pathMatch
— нет. Переход по маршруту /contacts/director
отобразит компонент, указанный в redirectTo
для contacts
. Это происходит потому что при навигации на дочерний маршрут, Router
проходит по дереву иерархии все его родительские URL и при попадании на /contacts
сработает редирект.
Чтобы избежать этого в Angular routing указывается свойство pathMatch
со значением full
. И тогда перенаправление сработает только когда точно будет запрошен URL /contacts
.
Для обработки несуществующих маршрутов необходимо использовать path
со значением **
(означает любой маршрут). Таким образом, если запрошенный URL не найдет соответствий в определенной конфигурации, то маршрутизатор отобразит компонент, указанный для **
.
В Angular маршрутизации важен порядок определения URL. При запросе маршрутизатор ищет первый подходящий маршрут в порядке их определения. Поэтому обработка любого URL всегда должна определяться в самом конце.
Определение маршрутов далее передается в качестве аргумента методу forRoot()
модуля RouterModule
.
Метод forRoot() используется только при определении набора URL корневого модуля. Для остальных (дочерних) модулей используется метод forChild().
Далее AppRoutingModule
импортируется в модуль, для которого создается навигация.
@NgModule({
imports: [
AppRoutingModule
],
declarations: [
AppComponent,
LoginRouteComponent,
HomeRouteComponent,
ProfileRouteComponent,
DirectorContactsRouteComponent
],
providers: [],
bootstrap: [AppComponent],
exports: []
})
Компоненты, на которые указывает Angular routing, подгружаются в место, где указана директива <router-outlet></router-outlet>
.
Для маршрутов, находящихся на самом верхнем уровне иерархии (LoginRouteComponent
и HomeRouteComponent
), родительским служит компонент, указанный в поле bootstrap
.
<div class="wrapper">
<app-nav></app-nav>
<main>
<router-outlet></router-outlet>
</main>
<app-footer></app-footer>
</div>
Для перехода на дочерний маршрут (child route) необходимо указать полный путь к нему начиная с самого верхнего родителя.
То есть, чтобы увидеть ProfileRouteComponent
, нужно в адресную строку браузера ввести /home/profile
. При этом HomeRouteComponent
должен содержать директиву <router-outlet></router-outlet>
, поскольку последующая навигация происходит только внутри него самого.
home-route.component.html
<div class="profile">
<h1>Home Page</h1>
<router-outlet></router-outlet>
</div>
Структура Angular routing определена, но как теперь перейти на описанные URL из шаблона? В AppComponent
используется компонент AppNavComponent
, в котором и определено меню приложения.
app-nav.component.html
<ul>
<li>
<a routerLink="/home" routerLinkActive="active-link"
>Home</a
>
</li>
<li>
<a
routerLink="/home/profile"
routerLinkActive="active-link"
>Profile</a
>
</li>
<li>
<a
routerLink="/contacts/director"
routerLinkActive="active-link"
>Director contacts</a
>
</li>
</ul>
Для перехода по заданным URL используется директива routerLink
, которая может быть указана не только у тега <a>
, но и у любого другого блочного HTML элемента.
Совместно с routerLink
используется директива routerLinkActive
. Она принимает название класса, который будет добавлен элементу (у которого указаны директивы) при активном URL, на который они ссылаются.
Как видно, все ссылки указываются от корня приложения (начинаются с /
). Но в Angular routing также возможно использование относительных ссылок.
Предположим, в HomeRouteComponent
должна содержаться ссылка на компонент ProfileComponent
. Поскольку URL для HomeComponent
(home) является родительским по отношению к URL ProfileRouteComponent
(profile). Ссылка может быть указана так:
<li>
<a routerLink="profile" routerLinkActive="active-link"
>Profile</a
>
</li>
Указание относительных ссылок позволяет избежать переписывания всех ссылок дочерних компонентов при изменении родительской.
Любое Angular приложение, реализующее внутри себя навигацию, имеет единственный экземпляр сервиса Router
, который хранит полную конфигурацию маршрутов и реализует API для работы с навигацией.
Наиболее часто используемым методом сервиса Router является navigate()
. В качестве первого параметра он принимает массив, где задается URL, а в качестве второго — объект с дополнительными параметрами запрашиваемого маршрута:
this.router.navigate(['profile', 3], {
queryParams: { id: 3 },
fragment: 'address',
})
Маршруты с параметрами¶
Часто возникает необходимость отображения данных в зависимости от какого-то параметра, например, id
. Эти параметры обычно передаются через URL.
Модернизируем маршрут profile
, который должен отображать данные профиля по запрашиваемому id
. Также укажем хлебные крошки через свойство data
.
{
path: 'home',
component: HomeComponent,
children: [{
path: 'profile/:id',
component: ProfileComponent,
data: {breadcrumbs: 'Profile info'}
}]
}
Получить значение параметра и свойства data
позволяет сервис ActivatedRoute
. В его экземпляре, определенном для компонента-маршрута, содержится полная информация.
profile.component.ts
@Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss'],
})
export class ProfileComponent {
constructor(private route: ActivatedRoute) {
console.log(this.route)
}
}
В консоль будет выведена полная информация о текущем URL. Необходимые свойства:
url
— наименование маршрута;params
— параметры Angular маршрутизации и их значения, указываемые при определении маршрута, например,id
в/profile/:id
;queryParams
— параметры строки запроса, например,id
в/profile?id=3
;fragment
— значениеhash
, например,address
в/profile#address
;data
— объект одноименного свойства, указываемого при определении маршрута.
В шаблоне параметры маршрутизации передаются следующим образом: в качестве значения директивы указывается массив, первое значение которого URL, второе — значение параметра данного URL.
<a [routerLink]="['profile', 3]">Profile</a>
Параметры строки запроса и hash
имеют следующую запись:
<a [routerLink]="['profile']" [queryParams]="{id: 3}"
>Profile</a
>
<a [routerLink]="['profile']" [fragment]="address"
>Profile</a
>
Множественная маршрутизация (named outlets)¶
В Angular различают основные и второстепенные маршруты. В пределах одного компонента может быть только один основной маршрут и сколько угодно второстепенных.
Реализуется это с помощью директивы <router-outlet></router-outlet>
. Основной URL соответствует стандартной записи директивы, второстепенный — записи директивы с атрибутом name.
Основные и второстепенные URL полностью независимы друг от друга. Для создания второстепенного маршрута при его определении нужно указать значения атрибута name директивы <router-outlet></router-outlet>
в свойстве outlet
.
Добавим в приложение второстепенный маршрут discount-info
, который будет отображать информацию о скидках.
Обновим определение Angular routing.
{path: 'discount-info', component: DiscountInfoComponent, outlet: 'discount'}
Обновим файл app.component.html
.
<div class="wrapper">
<app-nav></app-nav>
<main>
<div class="row">
<div class="col-9">
<router-outlet></router-outlet>
</div>
<div class="col-3">
<router-outlet name="discount"></router-outlet>
</div>
</div>
</main>
<app-footer></app-footer>
</div>
Теперь в приложении в правой колонке будет отображаться информация о скидках. Чтобы отобразить/скрыть значение второстепенного маршрута, можно выполнить следующее:
<a [routerLink]="[{outlets: {discount: null}}]"
>Hide discount</a
>
<a [routerLink]="[{outlets: {discount: 'discount-info'}}]"
>Show discount</a
>
Или из контроллера:
this.router.navigate([{ outlets: { discount: null } }])
this.router.navigate([
{ outlets: { discount: 'discount-info' } },
])
При отображении/скрытии обратите внимание на значение адресной строки браузера. В конце URL должна появляться/исчезать приписка.
...(discount:discount-info)