Как вы можете использовать canActivate Angular, чтобы отменить результат охраны?

Из угловой документации по canActivateкажется, вы можете использовать только canActivate охранники, чтобы разрешить переход к маршруту, если canActivate функция в конечном итоге возвращает true,

Есть ли какой-то способ сказать, "только перейти к этому маршруту, если canActivate класс оценивается как false"?

Например, чтобы не позволить зарегистрированным пользователям посещать страницу входа, я попробовал это, но это не сработало:

export const routes: Route[] = [
    { path: 'log-in', component: LoginComponent, canActivate: [ !UserLoggedInGuard ] },

Я получил эту ошибку в консоли:

ERROR Error: Uncaught (in promise): Error: StaticInjectorError[false]: 
  StaticInjectorError[false]: 
    NullInjectorError: No provider for false!
Error: StaticInjectorError[false]: 
  StaticInjectorError[false]: 
    NullInjectorError: No provider for false!

5 ответов

Интересная вещь в вашем вопросе - формулировка:

Можно ли как-то сказать: "переходите к этому маршруту только в том случае, если класс canActivate оценивается как false "?

А как вы выразили "интуитивное" решение:

{ path: 'log-in', component: LoginComponent, canActivate: [ !UserLoggedInGuard ] },

Что в основном говорит, что вам нужно negate результат UserLoggedInGuard@canActivate

Давайте рассмотрим следующую реализацию UserLoggedInGuard:

@Injectable()
export class UserLoggedInGuard implements CanActivate {
   constructor(private _authService: AuthService) {}

   canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
        return this._authService.isLoggedIn();
    }
} 

Далее, давайте посмотрим на решение, предложенное @Mike

@Injectable()
export class NegateUserLoggedInGuard implements CanActivate {    
    constructor(private _authService: AuthService) {}

   canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
        return !this._authService.isLoggedIn();
    }
}

Теперь подход в порядке, но тесно связан с (внутренней) реализацией UserLoggedInGuard, Если по какой-то причине реализация UserLoggedInGuard@canActivate изменения, NegateUserLoggedInGuard сломает.

Как мы можем избежать этого? Простое внедрение зависимости от злоупотребления:

@Injectable()
export class NegateUserLoggedInGuard implements CanActivate {    
  constructor(private _userLoggedInGuard: UserLoggedInGuard) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
     return !this._userLoggedInGuard.canActivate(route,state);
  }
}

Теперь это именно то , что вы выразили

canActivate: [ !UserLoggedInGuard ]

И лучшая часть:

  • Это не тесно связано с внутренней реализацией UserLoggedInGuard
  • Может быть расширен, чтобы манипулировать результатом более чем одного Guard учебный класс

У меня была аналогичная проблема - я хотел сделать страницу входа, которая была бы доступна только в том случае, если она не аутентифицирована, а панель инструментов доступна только в том случае, если вы аутентифицированы (и автоматически перенаправляете пользователя на соответствующий). Я решил это, сделав саму стражу логином + маршрут чувствительным:

Маршруты:

const appRoutes: Routes = [
  { path: 'login', component: LoginComponent, canActivate: [AuthGuard] },
  { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },

Охрана:

export class AuthGuard implements CanActivate {

  private login: UrlTree;
  private dash: UrlTree;

  constructor(private authSvc: AuthenticationService, private router: Router ) {
    this.login = this.router.parseUrl('login');
    this.dash = this.router.parseUrl('dashboard');
  }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree {
    if (this.authSvc.isSignedIn()) {
      if (route.routeConfig.path === 'login') {
        return this.dash;
      } else {
        return true;
      }
    } else {
      if (route.routeConfig.path === 'login') {
        return true;
      } else {
        return this.login;
      }
    }
  }
}

Думая о вашей проблеме, одним из решений может быть реализация защиты маршрута, которая выполняет логику в обратном порядке.

import { MyService } from "./myservice.service";
import { CanActivate, RouterStateSnapshot, ActivatedRouteSnapshot } from "@angular/router";
import { Injectable } from "@angular/core";

@Injectable()
export class MyGuard implements CanActivate {

    constructor(private myService: MyService) {}

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        return this.myService.isNotLoggedIn(); //if user not logged in this is true
    }
}

Я добавилdata.auth: booleanвариант для всех моих маршрутов (кроме тех, которые должны быть доступны в любом случае), например:

      const routes: Routes = [
  {
    path: '', // all
    component: HomeComponent,
  },
  {
    path: 'authenticate', // not authenticated
    component: AuthComponent,
    data: { auth: false },
    canActivate: [AuthGuard],
  },
  {
    path: 'account', // authenticated
    component: AccountComponent,
    data: { auth: true },
    canActivate: [AuthGuard],
  },
]

А затем в своей системе аутентификации я проверяю это свойство данных и действую соответственно:

      export class AuthGuard implements CanActivate {
  constructor(private readonly supabase: SupabaseService) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot,
  ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return route.data['auth'] ? !!this.supabase.session : !this.supabase.session
  }
}

Поместите данные в охранники

        {
    path: '',
    redirectTo: 'home',
    pathMatch: 'full'
  },
  {
    path: 'home',
    loadChildren: () => import('./modules/home/home.module').then(m => m.HomeModule),
    canActivate: [isAuthenticatedGuard],
  },
  {
    path: 'login',
    loadComponent: () => import('./modules/login/login.component').then(m => m.LoginComponent),
    canActivate: [isAuthenticatedGuard],
    data: { disableAccess: true }
  },
  {
    path: 'register',
    loadComponent: () => import('./modules/register/register.component').then(m => m.RegisterComponent),
    canActivate: [isAuthenticatedGuard],
    data: { disableAccess: true }
  }

код:

      
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    const url: string = state.url;
    const result = this.checkLogin(url);
    const { disableAccess = false } = route.data;
    const redirectMessage =
      "User is not logged - This routing guard prevents redirection to any routes that needs logging."; // TODO: Move it out

    if (result) {
      if (disableAccess) {
        console.log(redirectMessage);
        this.router.navigate(['home/main',]); //TODO: redirect to logout, and prepare application for logout
      }
    } else {
      console.log(redirectMessage);
      if (!disableAccess) {
        this.router.navigate(['login',]); //TODO: redirect to logout, and prepare application for logout
      }
    }

    return disableAccess ? !result : result;
  }

А затем просто манипулируйте им вот так или любым другим способом.

Во-первых: решение создать новую защиту и повторно использовать canActivate из противоположной защиты явно плохое. Могут быть редиректы, которые могут сработать от другого охранника, и вообще идея внедрить Guard другому охраннику - нехорошо.

Буду рад вашим комментариям, если у вас есть слова для меня.

Другие вопросы по тегам