Everything you need to know about route Guard in Angular
Tags: angular
Published: 2023-01-18 09:02:00

Routing is a significant aspect of any SPA application, and protecting these routes is often necessary. We may want to guard our routes for permission access or to prevent users from exiting a route by mistake if a form has not been submitted correctly.

Angular provides a set of built-in guards that can be easily used for various use cases.

In this article, I will demonstrate and explain each of the built-in guards provided and show you how to use them with some common examples.
<br />
## CanActivate

This is the most widely used guard. The `canActivate` guard is a route guard that allows or denies access to a route based on the logic specified in the guard. The method implemented in the guard returns a `boolean` , a `UrlTree`, a `Promise<boolean | UrlTree>` or an `Observable<boolean | UrlTree>`.

```typescript
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
```
When the guard is used on a route, the router will call the method defined in the guard before navigating to the route. If the method returns `true`, the navigation will proceed. If it returns `false`, the navigation will be canceled and the user will stay on the current route. If the method returns a promise or an observable, the router will wait for it to resolve before proceeding with the navigation. If the method return a `UrlTree` , the navigation will be canceled and the new navigation will be executed.

### Example:

```typescript
@Injectable({ providedIn: 'root' })
export class PermissionsService {
  private user = getUser();

  isAdmin(isAdmin: boolean) {
    return isAdmin ? user.isAdmin : false;
  }
}

@Injectable({ providedIn: 'root' })
export class IsAdminGuard implements CanActivate {
  private permission = inject(PermissionsService);
  
  canActivate(route: ActivatedRouteSnapshot) {
      const isAdmin: boolean = route.data?.['isAdmin'] ?? false;
      return this.permission.isAdmin(isAdmin);
  }
}

export const APP_ROUTES: [{
  path: 'dashboard',
  canActivate: [IsAdminGuard],
  data: {
    isAdmin: true,
  },
  loadComponent: () => import('./dashboard/admin.component'),
}]

```
This example illustrates the typical way of implementing a guard. We declare a class as a service that implements the `CanActivate` interface. In this scenario, we are checking if the user is an admin, as this route can only be accessed by admin users.

It is possible to pass `data` to our guard by setting properties inside the data property of the `Route` object.

**Warning: implementing guard as injectable services is going to be deprecated in v15.2 and removed in v17**

Injectable Class and `InjectionToken` -based guards are less configurable and reusable and require more boilerplate code. Additionally they cannot be inlined making them less powerful and more cumbersome.

{% embed https://github.com/angular/angular/pull/47924 %}

If you still want to do it this way or for backward compatibility, you will need to create a function to inject your service, like this:

```typescript
function mapToActivate(providers: Array<Type<{canActivate: CanActivateFn}>>): CanActivateFn[] {
  return providers.map(provider => (...params) => inject(provider).canActivate(...params));
}
const route = {
  path: 'admin',
  canActivate: mapToActivate([IsAdminGuard]),
};
```
<br />
### The new way:
```typescript
@Injectable({ providedIn: 'root' })
export class PermissionsService {
  isAdmin(isAdmin: boolean) {
    return isAdmin;
  }
}

export const canActivate = (isAdmin: boolean, permissionService = inject(PermissionsService)) => permissionService.isAdmin(isAdmin);

export const APP_ROUTES: [{
  path: 'dashboard',
  canActivate: [() => canActivate(true)],
  loadComponent: () => import('./dashboard/admin.component'),
 }]
```

It feels better, doesn't it ? With less boilerplate and more explicit code _(We don't need to set some properties on the `Route` data attribute that are often forgotten about in the previous approach)_. 

---

For the purpose of this article, all others examples will be implemented using the new approach.
<br />
## CanMatch

The `CanMatch` guard is a new feature that was introduced in **Angular v14.2**. It will activate the route and load the lazy-loaded component if all guards return true, otherwise it will navigate to the next route with the same name.

**Warning:** It's important to note that ONE route has to match otherwise you will encounter an error in your console.

```bash
ERROR Error: Uncaught (in promise): Error: NG04002: Cannot match any routes. 
URL Segment: 'dashboard'
```

### Example:

```typescript
@Injectable({ providedIn: 'root' })
export class PermissionService {
  isAllowed(permissions: Permission[]) {
    const user = ...
    return permissions.includes(user.permission);
  }
}

export type Permission = 'ADMIN' | 'USER' | 'MANAGER';

export const canMatch = (permissions: Permission[], permissionService = inject(PermissionsService)) =>
  permissionService.isAllowed(permissions);

export const APP_ROUTES: [
  {
    path: 'dashboard',
    canMatch: [() => canMatch(['ADMIN'])],
    loadComponent: () => import('./dashboard/admin.component'),
  },
  {
    path: 'dashboard',
    canMatch: [() => canMatch(['MANAGER'])],
    loadComponent: () => import('./dashboard/manager.component'),
  },
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard/everyone.component'),
  }
]
```
If we want to navigate to the `/dashboard` route, it will first check if user has the `ADMIN` permission, if it does, the router will load the `AdminComponent` otherwise the router will continue to the next possible route, and so on. We can set a fallback route specifically for this `dashboard` navigator, but we can also set a catch-all routes `**` which will catch all failed navigations:

```typescript
{
  path: '**',
  loadComponent: () => import('./not-found.component'),
}
```

**Note:** This guard can also return a `UrlTree` , which will cancel the previous navigation and trigger a new navigation.

```typescript
@Injectable({ providedIn: 'root' })
export class PermissionService {
  constructor(private router: Router) {}

  isAllowed(permissions: Permission[]) {
    if(!user) {
      return this.router.parseUrl('no-user');
    }
    // check permissions
  }
}
```
<br />
## CanActivateChild
This is quite similar to `CanActivate` and is often misunderstood.

To help clarify the differences, here is an example:

```typescript
export const APP_ROUTES = [
  {
    path: 'dashboard',
    canActivate: [() => true],
    canActivateChild: [() => true],
    loadComponent: () => import('./dashboard/no-user.component'),
    loadChildren: () => import('./child-routes').then((m) => m.CHILDREN_ROUTE),
  }
]

// inside child-routes
export const CHILDREN_ROUTE = [
  {
    path: 'manager',
    loadComponent: () => import('./dashboard/manager.component'),
  },
  {
    path: 'client',
    loadComponent: () => import('./dashboard/client.component'),
  },
];
```
The key differences between the two are: 
- If we navigate from the root `/`  to `/dashboard/manager` route, both `CanActivate` and `CanActivateChild` guards will be executed. However if we navigate between child components (from `/dashboard/manager` to `/dashboard/client`), only `CanActivateChild` will be executed. `CanActivate` is only executed when the parent component is not yet created.

- If we navigate only to the parent component, only `CanActivate` will be executed

- If we navigate to one child, and one of the guard inside `CanActivateChild` return false, the entire route is canceled and the parent will not be created.

- `CanActivate` is executed before `CanActivateChild` . If `CanActivate` return false, `CanActivateChild` will not be executed.

- We can replace `CanActivateChild` which `CanActivate` on every child route. 
 
## CanDeactivate

The `CanDeactivate` guard is used to control the navigation away from a route. It allows you to prevent the user from leaving a route or a component until some condition is met, or to prompt the user for confirmation before navigating away.

**Note:** It's commonly used when working with forms to prevent the user from navigating away if the form has been modified but not yet submitted. We can display a modal dialog with a warning message to notify the user of any unsaved changes.

### Example:

```typescript
export interface DeactivationGuarded {
  canDeactivate(): Observable<boolean> | Promise<boolean> | boolean;
}

@Component({
  standalone: true,
  imports: [RouterLink, ButtonComponent],
  template: `<button app-button routerLink="/">Logout</button>`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export default class NoUserDashboardComponent implements DeactivationGuarded {
  canDeactivate(): boolean | Observable<boolean> | Promise<boolean> {
    return false;
  }
}

export const APP_ROUTES = [
  {
    path: 'dashboard',
    canDeactivate: [(comp: DeactivationGuarded) => comp.canDeactivate()],
    loadComponent: () => import('./dashboard/no-user.component'),
  }
]
```
- `CanDeactivate` takes the component associated with the route and injects it into the function. 

**Note:** Like any guard, it can return a `UrlTree` to navigate to trigger a new navigation to a different route.
<br />
## CanLoad

`CanLoad` is a guard that is often used with `CanActivate` . It loads the lazy-loaded component is the guard returns true.

**Note: This guard has been deprecated in Angular v.15.1 and has been replaced by `CanMatch`**
<br />
## Tips/ Tricks
<br />
### Chaining guards
A guard property is of type `Array` which means multiple guards can be chained to a specific route.
- All guards will be executed in the specified order
- If one guard return false, the navigation will be cancelled
- If the first guard returns false, the other guards in the array will not be executed
- If one guard returns a `UrlTree` , the following guards will not be executed and the navigation will be rerouted.

```typescript
export const APP_ROUTES = [
  {
    path: 'dashboard',
    canActivate: [() => true, () => false, () => true],
    loadComponent: () => import('./dashboard/no-user.component'),
  }
]
```
In this example `dashboard` route will be canceled, and the last function will not be executed
<br />
### Routing to children component depending on parameters

Given the following example:

```typescript
export const CHILDREN_ROUTE = [
  {
    path: '',
    pathMatch: 'full',
    redirectTo: 'compA'
  },
  {
    path: 'compA',
    loadComponent: () => import('./dashboard/comp-a.component'),
  },
  {
    path: 'compB',
    loadComponent: () => import('./dashboard/comp-b.component'),
  },
];

export const APP_ROUTES = [
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard/no-user.component'),
    loadChildren: () => import('./child_routes').then((m) => m.CHILDREN_ROUTE),
  }
]
```
When we navigate to the `dashboard` , we will be redirected to `dashboard/compA` . But if we need to redirect to either `compA` or `compB` depending on an external parameter _(such as user permission or if a specific action has been taken, … )_, we can "trick" our routing with a guard that returns a `UrlTree` to redirect to the correct URL based on certain conditions.

```typescript
export const redirectTo = (router = inject(Router), userStore = inject(UserStore)) => {
  return userStore.hasDoneAction$.pipe(
    mergeMap((hasDoneAction) =>
      iif(
        () => hasDoneAction,
        of(router.createUrlTree(['dashboard', 'compA'])),
        of(router.createUrlTree(['dashboard', 'compB']))
      )
    )
  );
};

export const CHILDREN_ROUTE: Route[] = [
  {
    path: '',
    pathMatch: 'full',
    children: [],
    canActivate: [() => redirectTo()],
  },
  {
    path: 'compA',
    loadComponent: () => import('./dashboard/comp-a.component'),
  },
  {
    path: 'compB',
    loadComponent: () => import('./dashboard/comp-b.component'),
  },
];
```
**Note:** The `children` property with an empty array must be provided because each route definition must include at least one property among `component` , `loadComponent` , `redirectTo` , `children` or `loadChildren`

---

That's it for this article! You should now have no excuses not to guard your route properly.

I hope you learned new Angular concept. If you liked it, you can find me on [Twitter](https://twitter.com/laforge_toma) or [Github](https://github.com/tomalaforge).

> 👉 If you want to accelerate your Angular and Nx learning journey, come and check out [Angular challenges](https://github.com/tomalaforge/angular-challenges).