How to implement Angular AuthGuard that uses CanMatchFn and CanActivateFn? (How to convert Class Guards to Functional Guards)
Asked Answered
S

1

5

I have the following Angular AuthGuard:

@Injectable({
    providedIn: 'root',
})
export class AuthGuard implements CanActivate, CanLoad {
    constructor(private authService: AuthService, private router: Router) {}

    canActivate(
        next: ActivatedRouteSnapshot,
        state: RouterStateSnapshot,
    ): | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
        return this.isAuthenticated();
    }

    canLoad(
        route: Route,
        segments: UrlSegment[],
    ): | boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
        return this.isAuthenticated();
    }

    isAuthenticated(): | boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
        return this.authService.isAuthenticated$.pipe(
            take(1),
            tap((isAuthenticated: boolean) => {
                if (!isAuthenticated) {
                    this.router.navigate(['/account/login']);
                }
            }),
        );
    }
}

I updated to Angular 15:

Angular CLI: 15.2.0
Node: 16.19.1
Package Manager: npm 8.19.3
OS: darwin x64

Angular: 15.2.0

Package                         Version
---------------------------------------------------------
@angular-devkit/core            15.2.0
@angular-devkit/schematics      15.2.0
@schematics/angular             15.2.0

And now I'm getting the following TS depreciation note: enter image description here And a similar depreciation message for the CanLoad interface:

@deprecated — Use CanMatchFn instead
'CanLoad' is deprecated.

I tried to look for how to implement my AuthGuard with the recommended CanActivateFn and CanMatchFn, but I couldn't find a good resource on you to implement this with the ability to redirect to a certain route when the user is not authenticated, as it appears in the old CanActivate and CanLoad implementation above:

this.router.navigate(['/account/login']);

How to correctly implement a simple Angular AuthGuard that uses the new CanMatchFn and CanActivateFn?

Selfabnegation answered 27/2, 2023 at 12:33 Comment(0)
C
9

The simpliest way is to use inject(AuthGuard).canActivate as CanActivateFn.

If you want to refactor to drop the class it's similar :

const isAuthenticated = (): | boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> => {
    const authService = inject(AuthService);
    const router = inject(Router);
    return authService.isAuthenticated$.pipe(
        take(1),
        tap((isAuthenticated: boolean) => {
            if (!isAuthenticated) {
                this.router.navigate(['/account/login']);
            }
        }),
    );
}

const canActivate:CanActivateFn = isAuthenticated;
const canMatch:CanMatchFn = isAuthenticated;

For the depreciation of CanLoad just pass canMatch: [canMatch] in your router.


Edit: For futur references, this PR adds new helper functions to allow smooth migration from Class guard to functionnal guards.

  • mapToCanActivate([MyClassGuard])
  • mapToCanActivateChild([MyClassGuard])
  • mapToCanMatch([MyClassGuard])

etc. This will probably land in 15.3 or 16.0.

Clavichord answered 27/2, 2023 at 12:44 Comment(5)
Interesting. Thanks. Any idea why did the Angular team change the implementation from using the regular DI in an injectable class to using manual injection?Selfabnegation
You might want to read github.com/angular/angular/pull/47924Clavichord
By the way I added the new helper functions that will land in the next minor release !Clavichord
Can we replace here 'const' with 'export'? should we? Also, what do you do if that helper function isAuthenticated() is async and has some 'await' inside?Mingrelian
@Mingrelian The API supported returning Promises/Observable so no issue and yes you can export them to use them is other files !Clavichord

© 2022 - 2024 — McMap. All rights reserved.