// 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)
path
- путь для маршрутаcomponent
- компонент для URLpathMatch
- задает соответствие URL свойству PATH ('full
', 'prefix
'); свойство обязательно при наличии redirectTo
redirectTo
- редирект на другой URLchildren
- для задания дочерних маршрутов, которые отображают дополнительные компоненты во вложенных элементах router-outlet
, содержащихся в шаблоне компонента активацииoutlet
- для поддержки множественных компонентов outlet
resolve
- определяет действия, которые должны быть совершены перед активацией маршрутаcanActive
- управляем активацией маршрутаcanActiveChild
- управляем активацией дочернего маршрутаcanDeactivate
- управляем тем, когда маршрут может деактивироваться для активации нового маршрутаloadCildren
- для настройки модуля, который загружается только в случае необходимостиcanLoad
- загрузка модулей по требованиюИменно корневой компонент обеспечивает навигацию между разными компонентами. RouterOutlet
- директива (<router-outlet>
) станет заполнителем, где роутер отобразит view (при этом все предыдущие компоненты будут удалены).
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 задает URl, с которым будут сравниваться пути маршрутизации.
<base href="/">
Директива 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
. Активная ссылка с директивой [routerLink]
получает класс router-link-active
.
routerLinkActive
по умолчанию выполняет частичный поиск совпадения для активных URl. Однако это можно регулировать: если exact
равно true
, то происходит полное сопоставление URl.
<a routerLink="/about"
routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" >
О сайте
</a>
ActivatedRoute
- содержит информацию о маршруте связанную с компонентом, который загружен в outlet
.
snapshot
- возвращает объект ActivatedRouteSnapshot
, который описывает текущий маршрут.
url
- возвращает массив объектов URLSegment
, каждый из которых описывает один сегмент URl-адреса для текущего маршрутаparams
- возвращает объект Params
с описанием параметров URlqueryParams
- возвращает объект Params
с описанием параметров запроса URlfragment
- возвращает объект string, содержащий фрагмент URlPath
- строка со значением сегмента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);
}
}
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'];
})
}
}
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
предоставляет доступ к системе навигации из компонентов.
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('/');
}
Система маршрутизации уничтожает компонент после того, как он перестает отображаться.
// 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 на 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 { }
PathFromRoot
- массив объектов ActivatedRoute
, содержащий маршруты, использованные для сопоставления с текущим URlparen
t - объект ActivatedRoute
для родителя маршрутаfirstChild
- объект ActivatedRoute
(первый дочерний), использованный для сопоставления с текущим URlchildren
- массив объектов ActivatedRoute
(все дочерние маршруты), использованные для сопоставления с текущим URlЭто очень удобно, так как дочерний компонент, к примеру, не получает уведомления, если изменились параметры у родительского компонента.
Guard - механизм для выполнения проверок перед активацией и деактивацией маршрута.
resolve
- назначает guadrs, которые откладывают активацию маршрута до завершения какого-либо действия, например, нам нужно загрузить данные с бэк-да.canActivate
- назначает guadrs, которые определяют возможность активации маршрута.canActivateChild
- назначает guadrs, которые определяют возможность активации дочерних маршрутов текущего маршрутаcanDeactivate
- назначает guadrs, которые определяют возможность уйти с текущего маршрута.canLoad
- определяет может ли модуль загрузиться с использованием lazy loading.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
расширяет класс для решения задачи по активации маршрута, тем самым мы определяем 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
- определяет возможность активации дочерних маршрутов текущего маршрута. guard применяется к родительскому маршруту, при этом Метод canActivateChild
вызывается всякий раз при изменении дочерних маршрутов. CanActivateChild на angular.io
@NgModule({
imports: [
RouterModule.forRoot([
{
path: 'root',
canActivateChild: [CanActivateTeam],
children: [
{
path: 'team/:id',
component: Team
}
]
}
])
],
providers: [CanActivateTeam, UserToken, Permissions]
})
Деактивация маршрута происходит при уходе пользователя с маршрута. Полезно, например, если у пользователя остались несохраненные данные. 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 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 модулей мы можем настроить предзагрузку. Существует параметр 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. Event Binding. Передача и использование данных с помощью event Binding
TypeScript - это суперсет, расширяющий возможности JavaScript наличием типов, классов, интерфейсов и т.д. Браузер не умеет выполнять TypeScript файлы, по этому их нужно компилировать в JS.
Деактивации роута в Angular5 и контроль навигации