Angular Functional Guards - How to Use Functional Router Guards
In this post you will learn how to create functional guards inside Angular.
I already made a post How to make guards in Angular using classes. This is exactly what we used in Angular previously. But starting from Angular 15 classes approach is deprecated and obviously is not recommended.
Now we have a new functional approach. And actually I think that it changes quite a lot of things inside Angular. This is a first huge step to make Angular more functional. Previously we wrote everything like classes but now we start to write things functionally with the approach this is similar to React for example.
So the goal of this post is to take existing class guards and refactor it to a function.
What we have
Here I already prepared a project with Angular 15.
We have 2 routes Home and Private. In order to access private route we must have a token inside localStorage which is checked by our guard. Here is how it looks like.
@Injectable()
export class AuthGuardService implements CanActivate {
constructor(
private currentUserService: CurrentUserService,
private router: Router
) {}
canActivate(): Observable<boolean> {
return this.currentUserService.currentUser$.pipe(
filter((currentUser) => currentUser !== undefined),
map((currentUser) => {
if (!currentUser) {
this.router.navigateByUrl('/');
return false;
}
return true;
})
);
}
}
Here we injected currentUserService
which helps us to get currentUser$
and check if we logged in or not. As getting of current user is an asynchronous process we must filter undefined
value which is our initial value. After checking the current user we either get an object or null.
Rewrite
Now let's try to rewrite our class in functional style. In order to do that I want to create a new file auth.guard.ts
.
export const authGuard = () => {
return this.currentUserService.currentUser$.pipe(
filter((currentUser) => currentUser !== undefined),
map((currentUser) => {
if (!currentUser) {
this.router.navigateByUrl('/');
return false;
}
return true;
})
);
}
This code won't work as I just created a function and copied the content of canActivate
inside. The main problem is that we use here currentUserService
and router
which both need to be injected.
In order to write that in functional way we are using inject
function.
export const authGuard = () => {
const currentUserService = inject(CurrentUserService);
const router = inject(Router);
return currentUserService.currentUser$.pipe(
filter((currentUser) => currentUser !== undefined),
map((currentUser) => {
if (!currentUser) {
router.navigateByUrl('/');
return false;
}
return true;
})
);
};
As you can see we injected first CurrentUserService
and Router
as local properties.
The last thing that we are missing is to change the usage of our guard from class to a function.
import { authGuard } from '../auth.guard';
const routes: Routes = [
{
path: 'private',
component: PrivateComponent,
canActivate: [authGuard],
},
];
Here in canActivate
instead of the class we are using functional guard.
As you can see in browser it works exactly the same as before.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done