Guards

Последнее обновление: 17.11.2023

Guards позволяют ограничить навигацию по определенным маршрутам. Например, если для доступа к определенному ресурсу требуется наличие аутентификации или наличие каких-то других условий, в зависимости от которых мы можем предоставить пользователю доступ, а можем и не предоставить. То есть guards защищают доступ к ресурсу, собственно поэтому данные элементы и названы так: "guards", что с английского можно перевести как "защитники".

canActivate

canActivate представляет один из типов guards, который позволяет управлять доступом к ресурсу при маршрутизации. canActivate() должен представлять функцию, которая имеет следующее определение:

type CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) =>
	 Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;

получает два параметра - объекты ActivatedRouteSnapshot и RouterStateSnapshot, которые содержат информацию о запросе. ActivatedRouteSnapshot позволяет получить различную информацию из запроса, в том числе параметры маршрута и строки запроса. Например, если бы в маршруте использовался параметр id, то мы могли бы его здесь получить:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) : boolean{
		
		// получаем id
		//console.log(route.params["id"]);
		
		// остальной код
	}

Результат метода обычно представляет логическое значение true или false. Чтобы разрешить доступ к запрошенному ресурсу функция должна возвращать true. Чтобы отменить/запретить доступ, то возвращается false.

Для примера определим следующий проект приложения:

Guard in Angular

Рассмотрим его элементы. В папке src/app для примера имеется два компонента. В файле home.component.ts располагается следующий компонент HomeComponent:

import { Component} from "@angular/core";
 
@Component({
    selector: "home-app",
    template: `<h2>Главная</h2>`
})
export class HomeComponent { }

В файле about.component.ts определен следующий компонент AboutComponent:

import { Component} from "@angular/core";
 
@Component({
    selector: "about-app",
    template: `<h2>О сайте</h2>`
})
export class AboutComponent {  }

Допустим, мы хотим ограничить доступ к компоненту AboutComponent. Для этого добавим в папкt src/app определим файл about.guard.ts со следующим кодом:

import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router";

export const aboutGuard = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
    console.log(route.routeConfig?.path);    // можно получить различную информацию о маршрутах, параметрах и ит.д.
    return confirm("Вы уверены, что хотите перейти?");
};

Здесь определена функция aboutGuard, которая соответствует определению canActivate. Для тестирования обоих ситуаций здесь вызывается метод confirm(), который отображает диалоговое окно с выбором. Если пользователь нажмет на кнопку отмены, то метод confirm возвратит false. Если же пользователь подтвердит действие, то будет возвращено значение true.

Для демонстрации здесь также выводится запрошенный путь с помощью значения route.routeConfig.path. То есть при принятии решения, перенаправлять пользователя по запрошенному пути или нет, мы можем исследовать различную информацию

Определим в файле app.component.ts ссылки для перехода к компонентам:

import { Component} from "@angular/core";
import { RouterOutlet, RouterLink} from "@angular/router";

@Component({
    selector: "my-app",
    standalone: true,
    imports: [RouterOutlet, RouterLink],
    styles: `a {padding: 3px;}`,
    template: `<div>
                    <h1>Маршрутизация в Angular 17</h1>
					<nav>
                        <a routerLink="">Главная</a>
                        <a routerLink="/about">О сайте</a>
                    </nav>
                    <router-outlet></router-outlet>
               </div>`
})
export class AppComponent {}

А в файле app.config.ts определим и установим маршруты для компонентов:

import { ApplicationConfig } from "@angular/core";
import { provideRouter, Routes } from "@angular/router";

import {HomeComponent} from "./home.component";
import {AboutComponent} from "./about.component";
import { aboutGuard }   from "./about.guard";

// определение маршрутов
const appRoutes: Routes =[
    { path: "", component: HomeComponent},
    { path: "about", component: AboutComponent, canActivate: [aboutGuard]}
];

export const appConfig: ApplicationConfig = {
  providers: [provideRouter(appRoutes)]
};

Чтобы ограничить доступ по маршруту "/about", в определении этого маршрута прописывается параметр canActivate: [aboutGuard].

В итоге при попытке перехода по маршруту "/about", отобразится диалоговое окно с подтверждением перехода.

Guards in Angular

Внедрение сервисов

Нередко принятие решения о переходе на ресурс определяется какими-то хранимыми данными. Например, определим в папке src/app новый файл auth.service.ts:

import { Injectable } from "@angular/core";

@Injectable({
  providedIn: "root",		// глобальный сервис
})
export class AuthService {
  isLoggedIn = true;
  login(): void { this.isLoggedIn = true; }
  logout(): void { this.isLoggedIn = false;}
}

Здесь определен класс AuthService, который определен как глобальный сервис, а переменная isLoggedIn указывает, залогинен ли пользовать. Для демонстрации будем считать, что пользователь залогинен.

Изменим файл about.guard.ts, чтобы производить переход в зависимости от того, залогинен ли пользователь:

import {inject} from "@angular/core";
import {AuthService} from "./auth.service";

export const aboutGuard = () => {
    const authService = inject(AuthService);    // получаем сервис
    return authService.isLoggedIn
};

Фактически эта та же функция aboutGuard, только я убрал параметры, поскольку в данном случае они не нужны, и мы можем их убрать. Внутри фукции с помощью функции inject() получаем в качестве зависимости глобальный сервис AuthService и возвращаем значение его переменной isLoggedIn.

CanDeactivate

сanDeactivate также позволяет управлять переходами. Он предназначен для таких, к примеру, случаев когда пользователь вводит какие-то данные. Однако не сохраняет их и покидает страницу. В этом случае мы могли бы выдать пользователю какое-либо предупреждение или окно с подтверждением перехода, чтобы избежать потери введенных данных.

То есть, если сравнивать с сanActivate, сanActivate проверяет возможность перехода на определенный компонент, а сanDeactivate проверяет возможность ухода с определенного компонента. canDeactivate представляет функцию со следующим определением:

type CanDeactivateFn<T> = (component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, 
	nextState: RouterStateSnapshot) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;

Допустим, мы хотим управлять навигацией с компонента AboutComponent. Изменим его код следующим образом:

import { Component} from "@angular/core";
import { RouterLink } from "@angular/router";
  
@Component({
    selector: "about-app",
    standalone: true,
    imports: [RouterLink],
    template: `<h2>О сайте</h2>
                <button class="btn btn-default" (click)="save()">Сохранить</button>
                <a routerLink="">На главную</a>
                `
})
export class AboutComponent  { 
    saved: boolean = false;
    save(){
        this.saved = true;
    }
}

Для имитации функционала в класс AboutComponent добавлено свойство saved, которое указывает, сохранены ли данные. С помощью метода save(), который вызывается по нажатию на кнопку, мы можем управлять значением этой переменной. К примеру, нажали на кнопку, значит данные сохранены, и свойство saved равно true.

Для управления переходом с AboutComponent добавим в папку src/app новый файл exit.about.guard.ts:

import { AboutComponent }   from "./about.component";
 
export const exitAboutGuard=(component: AboutComponent) =>{
     
    if(!component.saved){
        return confirm("Вы хотите покинуть страницу?");
    }
    return true;
}

В функцию exitAboutGuard в качестве первого параметра - component передается тип компонента, с которого осуществляется переход - это AboutComponent. Благодаря этому мы можем учитывать состояние компонента при переходе, в частности, переменную saved. В данном случае, если this.saved равно false (то есть условно, если данные не сохранены), то выводим диалоговое окно для подтверждения действия.

Если функция возвращает true, то выполняется переход со страницы. Если false, то пользователь остается на странице.

Чтобы задействовать exitAboutGuard, изменим маршрут для AboutComponent в файле app.config.ts:

import { ApplicationConfig } from "@angular/core";
import { provideRouter, Routes } from "@angular/router";

import {HomeComponent} from "./home.component";
import {AboutComponent} from "./about.component";
import { exitAboutGuard }   from "./exit.about.guard";

// определение маршрутов
const appRoutes: Routes =[
    { path: "", component: HomeComponent},
    { path: "about", component: AboutComponent, canDeactivate: [exitAboutGuard]}
];

export const appConfig: ApplicationConfig = {
  providers: [provideRouter(appRoutes)]
};

Во-первых, в определении маршрута добавляется параметр

canDeactivate: [exitAboutGuard]

И теперь при попытки ухода с компонента AboutComponent (если saved == false), мы увидим диалоговое окно для подтверждения перехода:

CanDeactivate Guard in Angular
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850