Angular: роутинг, програмный роутинг, ссылки
Пример простого роутинга
Файл app-routing.module.ts и импортирование модулей происходит автоматически, если при установке нового приложения подтвердить наличие роутинга. Вот так выглядит простой роутинг в файле app-routing.module.ts:
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { InventoryComponent } from './inventory/inventory.component'; import { LocationComponent } from './location/location.component'; import { MoveDetalisComponent } from './move-detalis/move-detalis.component'; const routes: Routes = [ { path: '', component: InventoryComponent }, { path: 'location', component: LocationComponent }, { path: 'move-detalis', component: MoveDetalisComponent, children: [ { path: '', component: ListDetalisComponent }, { path: 'foo', component: FaqDetalisComponent}, { path: ':id', component: DetalComponent }, ] }, { path: '404', component: ErrorPageComponent }, { path: '**', redirectTo: '/404' }, //все несуществующие пути редиректим на 404 ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) export class AppRoutingModule {}
Вывод роутинга внутри коревого компонента (app.component.html) либо родительского компонента, если он имеет дочерных: <router-outlet></router-outlet>
Пример:
<div class="some-class"> <app-header></app-header> <router-outlet></router-outlet> </div>
Ссылки и их настройка для роутинга:
Пример ссылок:
<a routerLink="/about">About</a> <a [routerLink]=['/users']">Users</a>
Пример, как присваивать нужные стили активной ссылке:
<li routerLinkActive="active-style" [routerLinkActiveOptions]="{exact: true}"> <a routerLink="/">Home</a></li> <li routerLinkActive="active-style"><a routerLink="/about">Home</a></li> <li routerLinkActive="active-style"><a routerLink="/users">Home</a></li>
Обратите внимание, что для ссылки только со слешем (главная страница) пришлось прописать опцию exact: true, которая делает ее активной только при точном совпадении. Иначе все ссылки, которые начинаются с такого же начала получаются активными.
Динамические ссылки для списка «итерурумых компонент», выведенного при помощи *ng-if:
Например, мы хотим вывести список элементов из массива, при клике на которые они загрузятся с параметром в url.
<app-component-item *ngFor="let itemtEl or itemsList; let idx = index" [itemData]=[itemtEl] [index]="i" > </app-component-item>
Теперь в компоненте app-component-item просто добавим ссылки при выводе:
<a [routerLink]="[index]"> .... </a>
Програмный роутинг (вызывается внутри класса, а не шаблона):
Относительно корня сайта:
import {Router} from '@angular/router" ... constructor(private router: Router) {} onLoadAction() { this.router.navigate(['/profile']) }
Относительно текущего роута:
import {Router} from '@angular/router" ... constructor(private router: Router) {} onLoadAction() { this.router.navigate(['edit'], {relativeTo: this.route}) }
Смысл такого роутинга (относительно текущего роута) заключается в перемещениях, когда в данный момент мы уже пребываем на динамическом каком-то роуте, например, содержащем id в своем пути.
Вот пример более сложного вариант относительно текущего роута, который приведет туда же, куда верхний более простой:
this.router.navigate([''../', this.id, 'edit'], {relativeTo: this.route})
Получение параметров и других данных из объекта в роуте, включая id
Всего есть 2 способа получить параметры из роута(урла): статический и динамический (названия способов выдуманные).
1) Статический
Это когда мы просто внутри компонента (того что отображается при роуте с данным параметром) получаем параметр. Например:
import { ActivatedRoute } from '@angular/router'; .... ngOnInit(): void { const id: any = this.route.snapshot.paramMap.get('id'); console.log(id); }
2) Динамический
import { ActivatedRoute, Params } from '@angular/router'; ... ngOnInit(): void { this.route.params.subscribe((params: Params) => { this.id = +params['id']; this.fooServices.testGetIdFromComponent(this.id); }); } ...
При такой записи мы с сервиса fooServices.service.ts теперь можем получить нужный нам элемент по id:
testGetIdFromComponent(id: number) { return this.dataList[id]; }
Проверка на edit mode
Пример проверки на edit mode компонента (если true, то отрисовывается компонент с инпут полями для редактирования) с использованием роутов.
Например, site.ru/users/0 – режим будет false, так как есть id parametr, типизированный как число. А site.ru/users/0/edit либо site.ru/users/new будут false:
Защита роутов (без авторизации и т.п.)
Например, нужно сделать, чтобы на некоторые роуты нельзя было зайти без авторизации.
1. Сервис для авторизации (имитация сервера)
Создаем простой сервис /app/auth.service.ts для эмуляции авторизации с возвращением промиса при попытке получить значение.
/app/auth.service.ts:
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class AuthService { private isAuth: boolean = false; login() { this.isAuth = true; } logout() { this.isAuth = false; } isAuthenticated(): Promise<boolean> { return new Promise((resolve) => { setTimeout(() => { resolve(this.isAuth); }, 1000); }); } constructor() {} }
2. Гвард для проверки авторизации
1) Создаем файл в папке /app. Например, auth.guard.ts. Создаем в нем класс AuthGuard и имплементируемся от интерфейса CanActivate.
2) Создаем метод canActivate, который принимает параметры route и state, что видно в примере ниже.
Возвращаемое значение можно указать разным – Observablem или Promise по типу <boolean> либо просто boolean (any в примере, чтобы не было ошибки из-за предполагаемого TS underfined).
/app/auth.guard.ts:
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; import { Observable } from 'rxjs'; import { AuthService } from './auth.service'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean | any { return this.authService.isAuthenticated().then((isAuth): boolean | void => { if (isAuth) { return true; } else { this.router.navigate(['/'], { queryParams: { auth: false, }, }); } }); } }
3. Настраиваем роутеры, которые надо защитить
В файле с массивом роутов для каждого роута, который надо защитить, настраиваем поле canActivate, куда передаем массив гвардов (у нас он один).
app-routing.module.ts:
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AuthGuard } from './auth.guard'; import { InventoryComponent } from './inventory/inventory.component'; import { LocationComponent } from './location/location.component'; import { MoveDetalisComponent } from './move-detalis/move-detalis.component'; const routes: Routes = [ { path: '', component: LocationComponent }, { path: 'location', component: InventoryComponent, canActivate: [AuthGuard] }, <---- здесь защитили { path: 'move-detalis', component: MoveDetalisComponent, children: [{ path: ':id', component: LocationComponent }] }, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) export class AppRoutingModule {}
Защита дочерных компонент
1. Для этого в том же гварде (/app/auth.guard.ts) ниже создаем еще один метод под названием canActivateChild:
/app/auth.guard.ts:
... canActivateChild( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean> | Promise<boolean> | boolean { return this.canActivate(route, state); } ...
2. Прописываем в файле с роутами для компонента, у которого есть дочерные, ограничение canActivateChild (не canActivate, как для родителя):
app-routing.module.ts:
... const routes: Routes = [ { path: '', component: LocationComponent }, { path: 'location', component: InventoryComponent, canActivate: [AuthGuard] }, { path: 'move-detalis', component: MoveDetalisComponent, canActivateChild: [AuthGuard], // <--- здесь children: [{ path: ':id', component: LocationComponent }], }, ]; ...