39

My Angular app includes a simple AuthGuard as shown below and there has never been a problem with it. Recently, I upgraded my Angular version from 15.1.4 to 15.2.0 and since then, my IDE indicates that both CanActivate and CanActivateChild are deprecated.

The official Angular documentation for CanActivate says:

Deprecated: Use plain JavaScript functions instead.

How would I need to adjust the code below to get rid of the deprecated warning?

export class AuthGuard implements CanActivate, CanActivateChild {

    constructor(private authService: AuthenticationService) {}

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree  {
        return this.authService.checkLogin()
            .pipe(
                map(() => true),
                catchError(() => {
                    this.router.navigate(['route-to-fallback-page']);
                    return of(false);
                }
            )
        );
    }

    canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
        return this.canActivate(route, state);
    }
}
yivi
  • 42,438
  • 18
  • 116
  • 138
kellermat
  • 2,859
  • 2
  • 4
  • 21

5 Answers5

45

The trick is to rely on inject() for the injection of the instances you need :

export const canActivate: CanActivateFn = (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot
) => {
  const authService = inject(AuthenticationService);
  const router = inject(Router);

  return authService.checkLogin().pipe(
    map(() => true),
    catchError(() => {
      router.navigate(['route-to-fallback-page']);
      return of(false);
    })
  );
};

export const canActivateChild: CanActivateChildFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => canActivate(route, state);

inject() allows you to use the dependency injection when you are in an injection context. For example in a constructor or like here when passing a function to a core Angular feature.

You can also read about it on the depreciation list.


Edit:

Since v16, Angular provides some helper functions to convert classes to functionnal guards like mapToCanActivate :

@Injectable({providedIn: 'root'})
export class AdminGuard {
  canActivate() {
    return true;
  }
}

const route: Route = {
  path: 'admin',
  canActivate: mapToCanActivate([AdminGuard]),
};

You can find the others here.

Matthieu Riegler
  • 31,918
  • 20
  • 95
  • 134
  • 1
    Thanks for your reply. Your approach seems to work, yet I had to make a couple of small modifications to make it work. Is it okay when I edit the code in your answer accordingly? Afterwards I can make your answer the accepted answer ;-). – kellermat Feb 25 '23 at 13:53
  • 1
    Yes please do, I wrote it on top of my head ! – Matthieu Riegler Feb 25 '23 at 13:54
  • Just out of curiosity: How did you figure out this solution? I couldn't find an announcement of the deprecation in the release notes, let alone a detailed documentation on how to modify the deprecated code. – kellermat Feb 25 '23 at 14:00
  • 3
    I'm a modest contributor to the framework and follow closely the work of the Angular Team. I'll probably submit some changes to https://angular.io/guide/deprecations to include this topic. You won't be the last one to have this question ! – Matthieu Riegler Feb 25 '23 at 14:05
  • Wow, thats great. :-) Already when writing my question I had the feeling that it could be relevant for a larger number of Angular developers. I'm glad you could help with some "insider information". Thanks a lot :-) – kellermat Feb 25 '23 at 14:11
  • 3
    Actually https://angular.io/guide/deprecations#router-class-and-injectiontoken-guards-and-resolvers there already a good doc about ! :) – Matthieu Riegler Feb 25 '23 at 14:25
  • 2
    Is there a convention where such functions should be placed (I mean so that other Angular Devs instinctively know where to look for it)? – luke8800gts Mar 22 '23 at 10:03
  • 1
    I posted my solution to this deprecation. Could anyone explain, why this was changed anyways? – Marek Apr 13 '23 at 09:49
27

base on: https://angular.io/guide/router#preventing-unauthorized-access

old:

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  constructor(private router: Router) {}

  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean 
  {
    //your logic goes here
  }
}

new:

@Injectable({
  providedIn: 'root'
})
class PermissionsService {

  constructor(private router: Router) {}

  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
      //your logic goes here
  }
}

export const AuthGuard: CanActivateFn = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean => {
  return inject(PermissionsService).canActivate(next, state);
}

there is no need to change in your routes and can use it same as old:

 {
    path: 'Dashboard',
    canActivate: [AuthGuard],
    component: DashboardComponent
  }
ali pournemat
  • 581
  • 6
  • 7
7

As stated in the official doc :

Class-based Route guards are deprecated in favour of functional guards. An injectable class can be used as a functional guard using the inject function: canActivate: [() => inject(myGuard).canActivate()].

This means the canActivate method implementation via the implements CanActivate class in the child class is deprecated. Instead, you have to call this function directly from your routing module file where the declaration of the component happens.

// Routing Module File.ts

// Example 1 ~ Without parameters

{
    path: 'Dashboard',
    canActivate: [() => inject(AuthGuard).canActivate()], // AuthGuard is your same class as it was before
    component: DashboardComponent 
}

// Example 2 ~ With parameters

{
    path: 'Dashboard',
    canActivate: [(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => inject(AuthGuard).canActivate(next, state)],
    component: DashboardComponent
}

Hope this will help you or somebody else. Thanks!

Happy Coding :-)

Aman Kumar Gupta
  • 2,640
  • 20
  • 18
0

Anyone knows what is the reason behind this quite breaking change in minor version?

Some of us have a lot of logic inside Guard classes. Others have lots of guards. In such cases, to still keep the separation, I would go with the following approach (using static class methods):

export namespace AuthGuard {
    export const canActivate = (
        route: ActivatedRouteSnapshot, 
        state: RouterStateSnapshot
    ) => {
        const authService = inject(AuthenticationService);
        const router = inject(Router);

        return authService.checkLogin().pipe(
                map(() => true),
                catchError(() => {
                    router.navigate(['route-to-fallback-page']);
                    return of(false);
                }
            )
        );
    }

    export const canActivateChild = (
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot
    ) => canActivate(route, state);
}

Then in your router config:

{
    ...
    canActivate: [AuthGuard.canActivate],
    canDeactivate: [AuthGuard.canDeactivate],
}

Is there any downside with such approach?

Marek
  • 3,935
  • 10
  • 46
  • 70
  • 1
    The only downside I see is as useless class ! Just exporting your 2 functions would do the same ! – Matthieu Riegler Apr 13 '23 at 12:44
  • 3
    Not really. Imagine you have 20 guard functions in your application, each having canActivate and canDeactivate functions. You could import wrong functions then (you would end up with 20 functions with same name), unless you name those functions differently. Class solves this problem. That is why I prefer guard classes over functions, and I am sure I am not alone here. With the class approach, everything is encapsulated. – Marek Apr 13 '23 at 14:05
  • You're reimplementing a namespace without naming it so. – Matthieu Riegler Apr 13 '23 at 14:20
  • That is good point actually. Namespace would be a good choice here. I will update my answer. But still, any clue why Angular is going away from guards as clasess? – Marek Apr 13 '23 at 18:02
  • 1
    You can see the rationale on this PR and this comment in particular : https://github.com/angular/angular/pull/47924#issuecomment-1297711017 – Matthieu Riegler Apr 13 '23 at 18:58
0

As mentioned in Angular Doc that Class based guards are deprecated here doc link

So to overcome CanActivate Deprecated issue you need to write function based route guards as mentioned in angular doc. for that inject() can help you for injecting dependency which you done in constructor of class based guard.

Converted your code w.r.t. functional auth guard:

 export const authGuard: CanActivateFn = (
   route: ActivatedRouteSnapshot,
   state: RouterStateSnapshot
 ): Observable<boolean | UrlTree | Promise<boolean | UrlTree | boolean | UrlTree = {

    
    // here injecting dependencies
    const authService = inject(AuthenticationService);
    const router = inject(Router);


    // here you existing implementation of canActivate
    return authService.checkLogin().pipe(
     map(() = true),
     catchError(() = {
       void router.navigate(['route-to-fallback-page']);
       return of(false);
     })
   );
 };
Gomtesh Hatgine
  • 539
  • 4
  • 13