Angular Functional Guards - How to Use Functional Router Guards
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.

Guards

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.

Starting project

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.

Starting project

As you can see in browser it works exactly the same as before.

And actually if you want to learn Angular with NgRx from empty folder to a fully functional production application make sure to check my Angular and NgRx - Building Real Project From Scratch course.

📚 Source code of what we've done