Маршрутизация Angular подробное руководство
Порядок настройки роутинга
// app.module.ts
// I
import { RouterModule } from '@angular/router'
@NgModule({
imports: [
// II
RouterModule.forRoot([{
path: 'welcome',
component: WelcomeComponent
}, {
path: '',
redirectTo: 'welcome',
pathMatch: 'full'
}, {
path: '**',
component: PageNotFoundComponent
}
]),
/// III (index.html)
Свойства Routes
path
- путь для маршрутаcomponent
- компонент для URLpathMatch
- задает соответствие URL свойству PATH ('full
', 'prefix
'); свойство обязательно при наличииredirectTo
redirectTo
- редирект на другой URLchildren
- для задания дочерних маршрутов, которые отображают дополнительные компоненты во вложенных элементахrouter-outlet
, содержащихся в шаблоне компонента активацииoutlet
- для поддержки множественных компонентовoutlet
resolve
- определяет действия, которые должны быть совершены перед активацией маршрутаcanActive
- управляем активацией маршрутаcanActiveChild
- управляем активацией дочернего маршрутаcanDeactivate
- управляем тем, когда маршрут может деактивироваться для активации нового маршрутаloadCildren
- для настройки модуля, который загружается только в случае необходимостиcanLoad
- загрузка модулей по требованию
Директивы RouterOutlet
Именно корневой компонент обеспечивает навигацию между разными компонентами. RouterOutlet
- директива (<router-outlet>
) станет заполнителем, где роутер отобразит view (при этом все предыдущие компоненты будут удалены).
Именованные элементы router-outlet
router-outlet может быть несколько. Отсюда следует, что по одному маршруту можно вывести несколько компонентов, загрузив их в разные router-outlet
.
Чтобы отличать элементы router-outlet
используется атрибут name
. router-outlet
без атрибута name
является первичным, что равносильно outlet: "primary"
.
<div>
<router-outlet name="left"></router-outlet>
</div>
<div>
<router-outlet name="right"></router-outlet>
</div>
let routing = RouterModule.forChild([
{
path: "",
component: TestComponent,
children: [
{
path: "",
children: [
// свойство outlet используется для назначения router-outlet
{ outlet: "primary", path: "", component: FirstComponent, },
{ outlet: "left", path: "", component: SecondComponent, },
{ outlet: "right", path: "", component: SecondComponent, },
]
},
]
}
Элемент base
Элемент base задает URl, с которым будут сравниваться пути маршрутизации.
<base href="/">
Директива routerLink
Директива routerLink
приказывает Angular выбрать в качестве целевого маршрута результат значения(выражения) директивы routerLink
.
<a routerLink=""
routerLinkActive="active"
[routerLinkActiveOptions]="{exact:true}">
Главная
</a>
<a routerLink="/about"
routerLinkActive="active">
О сайте
</a>
Выражения для routerLink
задаются в виде массива, значения которого соответствуют каждому конкретному сегменту.
<a [routerLink]="['item', 'edit', '7']">item 7</a>
Директива routerLinkActive
Для стилизации активных ссылок применяется специальная директива routerLinkActive
. Активная ссылка с директивой [routerLink]
получает класс router-link-active
.
routerLinkActive
по умолчанию выполняет частичный поиск совпадения для активных URl. Однако это можно регулировать: если exact
равно true
, то происходит полное сопоставление URl.
<a routerLink="/about"
routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" >
О сайте
</a>
Служба ActivatedRoute и параметры маршрута
ActivatedRoute
- содержит информацию о маршруте связанную с компонентом, который загружен в outlet
.
Свойство ActivatedRoute
snapshot
- возвращает объект ActivatedRouteSnapshot
, который описывает текущий маршрут.
Свойства ActivatedRouteSnapshot
url
- возвращает массив объектовURLSegment
, каждый из которых описывает один сегмент URl-адреса для текущего маршрутаparams
- возвращает объектParams
с описанием параметров URlqueryParams
- возвращает объектParams
с описанием параметров запроса URlfragment
- возвращает объект string, содержащий фрагмент URl
Свойства URLSegment
Path
- строка со значением сегментаparameters
- индексированная коллекция параметров
// определение маршрутов
const appRoutes: Routes =[
{ path: 'item/:id', component: MyComponent},
{
path: 'products/:id/edit',
component: ProductEditComponent
}
Маршрут вида 'item/:id
' будет соответствовать любому URl-адресу из двух сегментов, первый из которых будет содержать item
. Второй сегмент будет содержать значение, которое присвоится параметру с именем id
. Mы сможем обратиться к компоненту с запросом типа /item/7
, и число 7
будет представлять параметр id
.
import { Component, OnInit} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
export class ProductDetailComponent implements OnInit {
constructor(private productService: ProductService,
private route: ActivatedRoute) {
// плохая практика вызывать в конструкторе "тяжелые" операции (в том числе и связанные с route)
// поэтому лучше его использовать в ngOnInit()
}
ngOnInit(): void {
// первый вариант вытащить параметры от роутинга
// snapshot - параметры route на момент инициализации
let id = +this.route.snapshot.params['id'];
// Анализ сегмента URl:
console.log(this.route.snapshot.url[1].path);
}
}
Реагируем на текущие изменения навигации при помощи ActivatedRoute
Свойства класса ActivatedRoute относящиеся к Observable:
url
- возвращаетObservable<UrlSegment[]>
: набор сегментов URl при изменении маршрутаparams
- возвращаетObservable<Params>
: набор параметров при изменении маршрутаqueryParams
- возвращаетObservable<Params>
: набор параметров запроса при изменении маршрутаfragment
- возвращаетObservable<string>
: фрагмент URl при изменении маршрута
export class ProductDetailComponent implements OnInit {
constructor(private route: ActivatedRoute) {
// подписываемся на параметры
this.route.params.subscribe(
params => {
let id = +params['id'];
this.getProduct(id);
}
);
// подписываемся на GET-параметры
this.route.queryParams.subscribe((params: Params) => {
this.color = params['color'];
this.year = params['year'];
})
}
}
Множественные параметры и routerLink
const routes: Routes = [
{ path: "item/:mode/:id", component: ItemComponent },
{ path: "item/:mode", component: ItemComponent },
{ path: "", component: MainComponent }
]
Mы сможем обратиться к компоненту с запросом типа /item/edit/7
:
>a [routerLink]="['item', 'edit', '7']">item 7>/a>
Выражения для routerLink задается в виде массива, значения которого соответствуют каждому конкретному сегменту.
Дополнительные(необязательные) параметры маршрута
>a
[routerLink] = "['/products', product.id, 'edit', { name: 'John', city: 'Moscow' }]">
Edit
>/a>
В браузере: http://localhost:3000/products/1/edit;name=John;city=Moscow
Получить в компоненте можно точно также как и для основных параметров (см. пример выше).
Класс Router
Класс Router
предоставляет доступ к системе навигации из компонентов.
Свойства и методы Класса Router
navigated
- возвращаетtrue
, если было хотя бы одно событие навигации, иначеfalse
.url
- возвращает активный URl-адресisActive(url, exact)
возвращаетtrue
, если заданный URl совпадает с URl, определенным активным маршрутом.events
- возвращает объект типаObservable<Event>
, который может использоваться для отслеживания навигационных изменений.navigateByUrl(url, extras)
- делаем навигацию к заданному URl-адрему.navigate(commands, extras)
- делаем навигацию по массиву сегментов.
gotoDetail(): void {
this.router.navigate(['/detail', this.selectedHero.id]);
// { path: 'detail/:id', component: HeroDetailComponent },
}
gotoBack(): void {
this.router.navigateByUrl('/');
}
Система маршрутизации уничтожает компонент после того, как он перестает отображаться.
Навигация с queryParams и хэшем
// cars?color=pink&year=1975#pic
openPage() {
this.router.navigate(['./cars', 8, 'opel'], {
// ?color=pink&year=1975
queryParams: {
color: 'pink',
year: 1975
},
// хэш: #pic
fragment: 'pic'
});
}
События навигации
Некоторые компоненты должны знать о выполнении навигации, неважно задействованы они в навигации или нет. Свойство events
объекта router
возвращает объект типа Observable<Event>
, который может использоваться для отслеживания навигационных изменений.
Типы Event для Router.events
Event на angular.io
NavigationStart
- триггер на начало навигацииRoutesRecognized
- триггер на 'узнавание' системной марщрутаNavigationEnd
- триггер на конец навигацииNavigationCancel
- триггер на отмену навигацииNavigationError
- триггер на ошибку навигации
export class TestComponent {
public testProp = 1;
constructor(router: Router) {
router.events
.filter(e => e instanceof NavigationEnd || e instanceof NavigationCancel)
.subscribe(e => { this.testProp = null; });
}
}
Универсальные маршруты
**
- так обозначается путь, который может соответствовать любому URl.
@NgModule({
imports: [
//II
RouterModule.forRoot([{
path: '**',
component: PageNotFoundComponent
}
]),
Перенаправление маршрутов
Перенаправление маршрутов определяется при помощи свойства redirectTo
, которое задает URl на который произойдет перенаправление. При перенаправлении должно быть задано свойства pathMatch (со значениями: full (URl полностью совпадает со значением совйства path), prefix (URl начинается с заданного пути))
// определение маршрутов
const routes: Routes = [
// pathMatch:'full' указывает, что запрошенный адрес должен полностью соответствовать маршруту
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'detail/:id', component: HeroDetailComponent },
{ path: 'heroes', component: HeroesComponent }
];
Дочерние маршруты
Дочерние маршруты позволяют компонентом реагировать на изменении URl-адреса, путем вставки в шаблон компонентов элемента router-outlet
.
Дочерние маршруты определяются в свойстве children
. Компоненты выбранные как дочерние маршруты отображаются в router-outlet
, который определен в родительском компоненте.
@NgModule({
imports: [
// Определение маршрутов для feature модуля
RouterModule.forChild([
// "" так как подгружаем по lazy loading и в основном роутинге уже прописан путь, который зайдет сюда
{ path: "", component: PhraseListComponent },
{ path: "phrase/:id", component: PhraseDetailsComponent },
{ path: "test-animation", component: TestAnimationComponent },
{
path: 'cars',
component: CarsPageComponent,
children: [{
path: ':id/:name',
component: CarPageComponent, canActivate: [ CarsGuardTestGuard ]
}
]
}
])
],
exports: [
RouterModule // делаем re-export модуля для использования директив при маршрутизации,
// благодаря этому данный RouterModule будет доступен в модуле,
// который импортирует данный модуль
]
})
export class PhrasesRoutingModule { }
Свойства ActivatedRoute для обращения к частям маршрута
PathFromRoot
- массив объектовActivatedRoute
, содержащий маршруты, использованные для сопоставления с текущим URlparen
t - объектActivatedRoute
для родителя маршрутаfirstChild
- объектActivatedRoute
(первый дочерний), использованный для сопоставления с текущим URlchildren
- массив объектовActivatedRoute
(все дочерние маршруты), использованные для сопоставления с текущим URl
Это очень удобно, так как дочерний компонент, к примеру, не получает уведомления, если изменились параметры у родительского компонента.
Guards
Guard - механизм для выполнения проверок перед активацией и деактивацией маршрута.
Свойства для использования guards:
resolve
- назначает guadrs, которые откладывают активацию маршрута до завершения какого-либо действия, например, нам нужно загрузить данные с бэк-да.canActivate
- назначает guadrs, которые определяют возможность активации маршрута.canActivateChild
- назначает guadrs, которые определяют возможность активации дочерних маршрутов текущего маршрутаcanDeactivate
- назначает guadrs, которые определяют возможность уйти с текущего маршрута.canLoad
- определяет может ли модуль загрузиться с использованием lazy loading.
resolve
resolve
- назначает guadrs, которые откладывают активацию маршрута до завершения какого-либо действия, например, нам нужно загрузить данные с бэк-да.
resolve
- это классы, определяющие метод resolve
с двумя аргументами:
route: ActivatedRouteSnapshot
- текущее состояние активногоroute
state: RouterStateSnapshot
- текущее состояние всегоroute
Допустимые значения, которые возвращает метод resolve
, при которых произойдет активация нагого маршрута:
Observable<any>
- когда срабатывает observerPromise<any>
- при обработке Promiseany
- любой результат
Определяем Resolvers как отдельный сервис:
// app/products/product-resolver.service.ts
@Injectable()
export class ProductResolver implements Resolve<IProduct> {
constructor(private productService: ProductService){ }
// route - текущее состояние активного route
// state - текущее состояние всего route
resolve(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<IProduct> {
let id = +route.params['id'];
let product = this.productService.getProduct(id);
return product;
}
}
Добавляем вышеописанный сервис в роутинг:
//app/products/product.module.ts
@NgModule({
imports: [
RouterModule.forChild([{
{
path: 'products/:id',
component: ProductDetailComponent,
// key - имя того что ресолвить (product), ProductResolver - resolver
resolve: {product: ProductResolver}
},
],
providers: [
ProductResolver,
ProductService
]
Модифицируем компонент для работы с resolver:
//products/product-detail.component.ts
export class ProductDetailComponent implements OnInit {
pageTitle: string = 'Product Detail';
product: IProduct;
errorMessage: string;
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
// продукт загруженные resolver
this.product = this.route.snapshot.data['product'];
}
}
Также удобно в resolver обрабатывать ошибки и, например, перенаправлять пользователя на нужную страницу.
canActivate
Интерфейс CanActivate
расширяет класс для решения задачи по активации маршрута, тем самым мы определяем guard по активации маршрутов. Метод canActivate
- возвращает логическое значение, указывающее на необходимость активации компонента. Удобно для проверки паролем и т.д.
Допустимые значения, которые возвращает метод CanActivate
:
boolean
- обычно при синхронной операции;true
- добро,false
- отказ.Observable<boolean>
- асинхронная операция; Angular ожидает, когда Observable выдаст значение.Promise<boolean>
- асинхронная операция; Angular ожидает, как разрешится Promise.
// auth-guard-admin.service.ts
@Injectable()
export class AuthGuardAdmin implements CanActivate {
// Observable<boolean>|Promise<boolean>|boolean - возможные результаты работы метода
// Если return true, то маршрут будет активирован, иначе - нет
constructor(public auth: AuthService, private router: Router) {}
canActivate() {
return this.auth.isAdmin;
}
}
// routing.module.ts
const routes: Routes = [
//...
{ path: 'delete-article', component: DeleteArticleComponent, canActivate: [AuthGuardAdmin] },
//...
И не забудьте добавить AuthGuardLogin
в providers
:
// app.module.ts
//...
providers: [
AuthGuardLogin
//...
canActivateChild
Метод canActivateChild
- определяет возможность активации дочерних маршрутов текущего маршрута. guard применяется к родительскому маршруту, при этом Метод canActivateChild
вызывается всякий раз при изменении дочерних маршрутов. CanActivateChild на angular.io
@NgModule({
imports: [
RouterModule.forRoot([
{
path: 'root',
canActivateChild: [CanActivateTeam],
children: [
{
path: 'team/:id',
component: Team
}
]
}
])
],
providers: [CanActivateTeam, UserToken, Permissions]
})
canDeactivate
Деактивация маршрута происходит при уходе пользователя с маршрута. Полезно, например, если у пользователя остались несохраненные данные. CanDeactivate на angular.io
Класс canDeactivate
получает три аргумента:
component: T
- деактивизируемый компонентcurrentRoute: ActivatedRouteSnapshot
currentState: RouterStateSnapshot
@Injectable()
class CanDeactivateTeam implements CanDeactivate<TeamComponent> {
constructor(private permissions: Permissions, private currentUser: UserToken) {}
canDeactivate(
component: TeamComponent,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState: RouterStateSnapshot
): Observable<boolean>|Promise<boolean>|boolean {
return this.permissions.canDeactivate(this.currentUser, route.params.id);
}
}
@NgModule({
imports: [
RouterModule.forRoot([
{
path: 'team/:id',
component: TeamCmp,
canDeactivate: [CanDeactivateTeam]
}
])
],
providers: [CanDeactivateTeam, UserToken, Permissions]
})
Lazy
Декомпозиция модулей (разбиение на модули) нужна в том числе для Lazy Loading (ленивая загрузка) - данную особенность предоставляют модули. Обычно приложение загружает весь js со всеми модулями, но зачем нам загружать все страницы, к примеру, если тот же юзер на второстепенные страницы может не перейти? Незачем. Поэтому мы можем вынести второстепенные страницы в отдельные модули и загружать их по необходимости.
Вот и рассмотрим на примере роутинга, то есть подключение модуелей с Lazy Loading по мере необходимости. Как видите, мы подключаем модуль указав путь, а не через import
, так как import
загрузит модуль сразу, а нам это не надо, также удалите import
{ PhrasesModule }
в app.module.ts. Мы не будем использовать import
, чтобы не webpack сразу не подключил наш файл в приложение.
loadChildren
- значением данного свойства является строка, чтобы webpack ничего заранее не загрузил.
const routes: Routes = [
{ path: '', component: AboutComponent },
// LAZY LOADING - Очень актуально на больших приложениях (актуально, например, подгружать модуль лишь после авторизации
// пользователя этим мы ускоряем первоначальную загрузку приложения в разы)
// Без "lazi loading" модуль PhrasesModule подгружался в app.module.ts в imports
// и мы убираем из его app.module.ts
// и настроим его роутинг на 'подгрузка по требованию'
// Значение loadChildren является строкой (путь до нашего модуля), чтобы webpack его заранее не загрузил
// #PhrasesModule - этим указываем какой класс подгрузить
// для того чтобы не было роута phrases/phrases нужно заменить в phrases.module.ts { path: "phrases", component: PhraseListComponent },
// на { path: "", component: PhraseListComponent }
// canLoad: [AuthGuardAdmin] - guard для ленивой загрузки
{ path: 'phrases', loadChildren: './test/phrases.module#PhrasesModule', canLoad: [CanLoadChildModuleGuard] }
];
@NgModule({
imports: [
RouterModule.forRoot(
routes,
{
initialNavigation: 'enabled',
preloadingStrategy: PreloadAllModules
}
)
],
exports: [ RouterModule ]
})
import { RouterModule } from "@angular/router";
// роутинг для подгружаемого модуля:
@NgModule({
imports: [
// Метод forRoot должен использоваться только в AppModule
RouterModule.forChild([
// "" так как подгружаем по lazy loading и в основном роутинге уже прописан путь, который зайдет сюда
{ path: "", component: PhraseListComponent },
{
path: 'cars',
component: CarsPageComponent,
children: [{
path: ':id/:name',
component: CarPageComponent, canActivate: [
CarsGuardTestGuard
]
}
]
}
])
],
exports: [
RouterModule
// делаем re-export модуля для использования директив при маршрутизации,
// благодаря этому данный RouterModule будет доступен в модуле, который импортирует данный модуль
]
})
export class PhrasesRoutingModule { }
Теперь при переходе на страницу phrases
в консоли вы сможете увидеть подгружаемый chank.
Отметьте, что для Guard вместо canActivate: [CanLoadChildModuleGuard]
нужно писать canLoad: [CanLoadChildModuleGuard]
Предзагрузка для lazy модулей
Для lazy модулей мы можем настроить предзагрузку. Существует параметр preloadingStrategy
со значениями:
PreloadAllModule
- когда приложение загружено, подтягиваются все lazy модули. Также мы можем создать свою стратегию (класс MyPreload
) - в нем мы дожны реализовать один метод preload
. Реализуем загрузку модуля по небольшой задержке:
export class MyPreload implements PreloadingStrategy {
preload(route:Route, load:() =< Observable<any>):Observable<any> {
if (route.data && route.data['nopreload']) {
return of(false);
}
return of(true).pipe(delay(5000), flatMap(_ => load()));
};
}
const routes:Routes = [
{path: '', component: HomeComponent},
{
path: 'lazy', loadChildren: './lazy/lazy.module#LazyModule',
data: {
nopreload: true
}
},
];
@NgModule({
imports: [RouterModule.forRoot(routes, { preloadingStrategy: MyPreload })],
exports: [RouterModule],
providers: [MyPreload] // !
})
export class AppRoutingModule {
}
Анимация при роутинге
router-outlet
заворачиваем в тег, на котором вешаем анимацию:
<main [@fadeAnimation]="getRouterOutletState(o)">
<router-outlet #o="outlet"></router-outlet>
</main>
Возможно, вам будет интересно
Кастомные элементы форм в Angular
От хорошего к великому: параметры входных данных, inputs в Angular
inputs
, как обязательных для компонентов и директив. Другими словами, теперь мы можем указать, что компонент или директива требует определенных входных данных inputs для правильной работы.