Angular Route Guards and Resolver Services Step by step Implementation and Top 10 Questions and Answers
 Last Update:6/1/2025 12:00:00 AM     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    23 mins read      Difficulty-Level: beginner

Angular Route Guards and Resolver Services

In Angular applications, routing plays a crucial role in managing navigation and display content based on the URL. However, there are scenarios where you need to perform specific actions before a route is activated, such as authentication checks, data fetching, or redirection. This is where Route Guards and Resolver Services come into play. These Angular features help manage the lifecycle of route activation effectively.

Route Guards

Route Guards allow you to control the activation, deactivation, and reuse of routes, ensuring that certain conditions are met before a route's components are instantiated. Angular provides five types of route guards:

  1. CanActivate
  2. CanActivateChild
  3. CanDeactivate
  4. Resolve
  5. CanLoad
CanActivate

The CanActivate guard checks permissions before activating a route. It is typically used to perform authentication checks, deciding whether a user can access a specific route. If the user is unauthorized, the guard can redirect them to the login page or another location.

Example:

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';

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

  constructor(private router: Router) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    const isAuthenticated = localStorage.getItem('token') !== null;

    if (!isAuthenticated) {
      this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
      return false;
    }

    return true;
  }
}

Usage in app-routing.module.ts:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from './auth.guard';
import { HomePageComponent } from './home-page/home-page.component';

const routes: Routes = [
  {
    path: 'home',
    component: HomePageComponent,
    canActivate: [AuthGuard]
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
CanActivateChild

The CanActivateChild guard is similar to CanActivate but specifically for child routes. It checks if a child route can be activated based on certain conditions.

Usage:

import { Injectable } from '@angular/core';
import { CanActivateChild, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class ChildAuthGuard implements CanActivateChild {

  constructor(private router: Router) {}

  canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    // Add child route specific checks here
    return true;
  }
}

Routes Configuration:

const routes: Routes = [
  {
    path: 'parent',
    canActivate: [AuthGuard],
    children: [
      {
        path: 'child',
        component: ChildPageComponent,
        canActivateChild: [ChildAuthGuard]
      }
    ]
  }
];
CanDeactivate

The CanDeactivate guard is used to decide whether to deactivate a route, typically to confirm unsaved changes with the user before leaving the route.

Example:

import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

export interface CanComponentDeactivate {
 canDeactivate: () => boolean | Promise<boolean>;
}

@Injectable({
  providedIn: 'root'
})
export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {

  canDeactivate(component: CanComponentDeactivate, route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Promise<boolean> {
    return component.canDeactivate();
  }
}

Component Implementation:

import { Component } from '@angular/core';
import { CanComponentDeactivate } from '../can-deactivate.guard';

@Component({
  selector: 'app-data-edit',
  template: '<p>Editing component with unsaved data.</p>'
})
export class DataEditComponent implements CanComponentDeactivate {

  canDeactivate(): boolean {
    const unsavedChanges = true; // Replace with your unsaved changes logic

    if (unsavedChanges) {
      return confirm('You have unsaved changes. Are you sure you want to leave?');
    }

    return true;
  }
}

Routes Configuration:

const routes: Routes = [
  {
    path: 'edit',
    component: DataEditComponent,
    canDeactivate: [CanDeactivateGuard]
  }
];
CanLoad

The CanLoad guard is used to determine if a lazy-loaded module can be loaded based on certain conditions. This guard is invoked only when the route is accessed for the first time.

Example:

import { Injectable } from '@angular/core';
import { CanLoad, Route } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class ModuleLoadGuard implements CanLoad {

  canLoad(route: Route): boolean {
    // Add conditions to check if the module should be loaded
    return true;
  }
}

Routes Configuration:

const routes: Routes = [
  {
    path: 'lazy',
    loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule),
    canLoad: [ModuleLoadGuard]
  }
];
Resolve

While not a traditional route guard, the Resolve service can be used in conjunction with route guards to pre-fetch data before a route is activated. This ensures that the required data is available when the component is initialized.

Usage:

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { DataService } from './data.service';

@Injectable({
  providedIn: 'root'
})
export class DataResolver implements Resolve<any> {

  constructor(private dataService: DataService) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
    const id = route.paramMap.get('id');
    return this.dataService.fetchData(id);
  }
}

Component Implementation:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-data-detail',
  template: '<p>Data Detail Component</p>'
})
export class DataDetailComponent implements OnInit {

  data: any;

  constructor(private route: ActivatedRoute) {}

  ngOnInit(): void {
    this.route.data.subscribe(data => {
      this.data = data;
    });
  }
}

Routes Configuration:

const routes: Routes = [
  {
    path: 'data/:id',
    component: DataDetailComponent,
    resolve: {
      data: DataResolver
    }
  }
];

Conclusion

Route Guards and Resolver Services are indispensable tools in Angular for managing route navigation and pre-fetching data. Byutilizing these features, developers can ensure that routes are activated under the correct conditions and that necessary data is available before a component is rendered, enhancing the user experience and application stability.




Examples, Set Route and Run the Application then Data Flow Step by Step for Beginners: Angular Route Guards and Resolver Services

Introduction

Angular provides a robust set of tools to handle routing within single-page applications, including routing guards and resolver services. These features help manage access to routes and load data before rendering a component. Understanding how to set up and use route guards and resolvers is crucial for creating a secure and responsive application.

In this guide, we'll walk through the process of setting up a basic Angular application with route guards and resolver services, providing step-by-step explanations of the data flow.

Step 1: Setup Angular Project

First, ensure you have Angular CLI installed on your system. If not, install it using npm.

npm install -g @angular/cli

Create a new Angular project:

ng new route-guard-resolver-app
cd route-guard-resolver-app

Step 2: Create Components and Services

  1. Components:
    • Dashboard Component
    • Login Component

Run the following commands to generate the components:

ng generate component dashboard
ng generate component login
  1. Service:
    • AuthService for handling user authentication
    • DataService for fetching data

Generate the services:

ng generate service auth
ng generate service data

Step 3: Implement AuthService

In auth.service.ts, create methods to handle login and logout functionalities.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private isAuthenticated = false;

  constructor() { }

  login() {
    this.isAuthenticated = true;
  }

  logout() {
    this.isAuthenticated = false;
  }

  isLoggedIn(): boolean {
    return this.isAuthenticated;
  }
}

Step 4: Create Route Guards

Angular provides four types of route guards, but here we will only cover the CanActivate guard for simplicity.

Run the following command to generate a route guard:

ng generate guard auth

Implement the CanActivate guard in auth.guard.ts:

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from './auth.service';

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

  constructor(private authService: AuthService, private router: Router) { }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): boolean {
      
    if (this.authService.isLoggedIn()) {
      return true;
    } else {
      this.router.navigate(['/login']);
      return false;
    }
  }
}

Step 5: Create Resolver Service

Generate a resolver service to fetch data before rendering a component.

ng generate resolver data

Implement the resolver in data.resolver.ts:

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { DataService } from './data.service';

@Injectable({
  providedIn: 'root'
})
export class DataResolver implements Resolve<any> {

  constructor(private dataService: DataService) { }

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any {
    return this.dataService.fetchData();
  }
}

Step 6: Implement DataService

In data.service.ts, create a method to fetch data (模拟 data fetching):

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class DataService {

  constructor() { }

  fetchData(): Observable<any> {
    return of([1, 2, 3, 4, 5]); // Simulated API call
  }
}

Step 7: Configure Routing

Update app-routing.module.ts to include route definitions, route guards, and resolvers:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';
import { LoginComponent } from './login/login.component';
import { AuthGuard } from './auth.guard';
import { DataResolver } from './data.resolver';

const routes: Routes = [
  { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard], resolve: { data: DataResolver } },
  { path: 'login', component: LoginComponent },
  { path: '', redirectTo: '/login', pathMatch: 'full' },
  { path: '**', redirectTo: '/login', pathMatch: 'full' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Step 8: Update Components

Update the components to display the fetched data and handle user authentication.

  1. Login Component: Include a form to log in.
<!-- login.component.html -->
<div>
  <h2>Login</h2>
  <button (click)="login()">Login</button>
</div>
// login.component.ts
import { Component } from '@angular/core';
import { AuthService } from '../auth.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent {

  constructor(private authService: AuthService, private router: Router) { }

  login() {
    this.authService.login();
    this.router.navigate(['/dashboard']);
  }
}
  1. Dashboard Component: Display fetched data.
<!-- dashboard.component.html -->
<div>
  <h2>Dashboard</h2>
  <ul>
    <li *ngFor="let item of data">{{ item }}</li>
  </ul>
  <button (click)="logout()">Logout</button>
</div>
// dashboard.component.ts
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth.service';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
  data: any;

  constructor(private authService: AuthService, private route: ActivatedRoute) { }

  ngOnInit(): void {
    this.data = this.route.snapshot.data['data'];
  }

  logout() {
    this.authService.logout();
  }
}

Step 9: Run the Application

Run the application using Angular CLI:

ng serve

Navigate to http://localhost:4200/. You should see the login form. On clicking the login button, you’ll be redirected to the dashboard, which will display the fetched data. If you try to access the dashboard without logging in, you’ll be redirected back to the login page.

Conclusion

In this guide, we set up a basic Angular application with route guards and resolver services. We learned how to create and implement the AuthService, AuthGuard, DataResolver, and DataService. Additionally, we configured routing to include these services. By following this step-by-step process, you should now have a clear understanding of how to use Angular's routing guards and resolver services to secure and enrich your application.




Top 10 Questions and Answers about Angular Route Guards and Resolver Services

What are Angular Route Guards and how do they work?

Route Guards are interfaces introduced in Angular for the purpose of allowing or preventing navigation to specific routes based on certain conditions. They can be used to control access to different parts of your application, perform checks before navigation occurs, and even redirect users. Angular provides several types of route guards, including:

  • CanActivate: Determines if a route can be activated.
  • CanActivateChild: Determines if children routes of a specific route can be activated.
  • CanDeactivate: Determines if a user can leave the current route.
  • Resolve: Performs data retrieval before a route is activated.
  • CanLoad: Prevents asynchronous loading of a module until a condition is met.

Route guards work by implementing one or more of these interfaces and defining the logic within their methods. Angular's router then uses these implementations to determine whether the navigation should proceed or be canceled.

How do I use a CanActivate guard?

A CanActivate guard is used to allow or deny access to a route based on specific criteria. Here’s an example of implementing a CanActivate guard for authorization:

  1. Create the Guard Class:
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from './auth.service';

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

    canActivate(
        next: ActivatedRouteSnapshot,
        state: RouterStateSnapshot): boolean {
        if (this.authService.isAuthenticated()) {
            return true;
        }
        this.router.navigate(['/login']);
        return false;
    }
}
  1. Register the Guard with Module Routes:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from './auth.guard';
import { AdminComponent } from './admin/admin.component';

const routes: Routes = [
  { path: 'admin', component: AdminComponent, canActivate: [AuthGuard] }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

In this setup, AdminComponent can only be accessed if isAuthenticated method of AuthService returns true. If not, the user is redirected to the /login route.

When would you use a CanDeactivate guard?

The CanDeactivate guard is useful when you need to check if a user is allowed to leave a particular route. This could be the case when there are unsaved changes in a form that might get lost. For example:

import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

export interface CanComponentDeactivate {
    canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}

@Injectable({
    providedIn: 'root'
})
export class ConfirmExitGuard implements CanDeactivate<CanComponentDeactivate> {

    canDeactivate(component: CanComponentDeactivate,
                  currentRoute: ActivatedRouteSnapshot,
                  currentState: RouterStateSnapshot):
        Observable<boolean>|Promise<boolean>|boolean {
        return component.canDeactivate ? component.canDeactivate() : true;
    }
}

Now in any component, you can implement canDeactivate interface:

import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { CanComponentDeactivate } from '../confirm-exit.guard';

@Component({templateUrl: './edit-form.component.html'})
export class EditFormComponent implements CanComponentDeactivate {
    canDeactivate(): Observable<boolean>|Promise<boolean>|boolean {
        // Your logic to check for unsaved changes
        return window.confirm('Do you really want to exit? Unsaved work will be lost.');
    }
}
  1. Register with module routes:
const routes: Routes = [
    {path: 'edit-form', component: EditFormComponent, canDeactivate: [ConfirmExitGuard]}
];

With this implementation, whenever a user tries to navigate away from EditFormComponent, canDeactivate method is called. It shows a confirmation dialog, if the user confirms the action, the navigation process continues; otherwise, it gets canceled.

Explain the Resolve service in Angular.

Resolve is another type of Angular route guard, but its purpose is different — to resolve route data before the route is activated. Data retrieved using resolvers becomes available in the routed component, making it easy to fetch data and have it ready when the component starts rendering.

For instance:

  1. Create the resolver class:
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable, of } from 'rxjs';
import { DataService } from './data.service';

@Injectable({
    providedIn: 'root'
})
export class DataResolver implements Resolve<any> {
    constructor(private dataService: DataService) {}

    resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>|any {
        const dataId = route.paramMap.get('id');
        return this.dataService.getDataById(dataId);
    }
}
  1. Register the resolve route property in the module:
const routes: Routes = [
    { path: 'detail/:id', component: DetailComponent, resolve: { resolvedData: DataResolver } }
];
  1. Injecting it into the routed component:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({templateUrl: './detail.component.html'})
export class DetailComponent implements OnInit {
    public data: any;

    constructor(private activatedRoute: ActivatedRoute) {}

    ngOnInit() {
        this.activatedRoute.data.subscribe(
          data => {
            this.data = data.resolvedData;
            console.log('Resolved Data:', this.data);
          }
        );
    }
}

Here, DataResolver fetches some data using DataService and makes it available to DetailComponent as resolvedData through the ActivatedRoute data observable.

Can multiple resolvers be defined for a single route?

Yes, you can define multiple resolvers for a single route. Each resolver can fetch different kinds of data:

const routes: Routes = [
  {
    path: 'detail',
    component: DetailComponent,
    resolve: {
      userData: UserDataResolver,
      postsData: PostsDataResolver
    }
  }
];

When navigating to '/detail', the DetailComponent will receive data from both UserDataResolver and PostsDataResolver. These will be accessible within the component via the activatedRoute.data observable.

How can I implement a CanLoad guard?

A CanLoad guard allows lazy-loaded modules to wait for an asynchronous operation (like authentication check) before the module and its routes are available for navigation.

Here’s an example of how to use CanLoad:

  1. Create a guard class which implements CanLoad:
import { Injectable } from '@angular/core';
import { CanLoad, Route, UrlSegment, UrlTree, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';

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

  canLoad(
    route: Route,
    segments: UrlSegment[]): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

    const isAuthenticated = this.authService.isAuthenticated();
    if (isAuthenticated) return true;

    this.router.navigate(['/login']);
    return false;
  }
}
  1. Register with lazy loaded modules in the router configuration:
const routes: Routes = [
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
    canLoad: [AuthCanLoadGuard]
  }
];

When the Angular router tries to load the AdminModule, it checks if the AuthCanLoadGuard returns true. If it does, the module's routes are fetched and the navigation can continue. Otherwise, the route loading is aborted and user is redirected to '/'login'.

How can route guards interact with other services in Angular?

Route guards can interact with any service in an Angular application just like any other component or service. Typically, this involves injecting services into the guard constructor and using them in the guard's methods to perform checks or operations prior to route activation, redirection, or deactivation.

For example, integrating HTTP client service with CanActivate guard:

@Injectable({
    providedIn: 'root'
})
export class HttpRouteGuard implements CanActivate {
    constructor(private http: HttpClient) {}
    
    canActivate(
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot): Observable<boolean> {
        return this.http.get('/api/has-access')
                       .pipe(map(response => response['hasAccess']));
    }
}

This code makes an HTTP call to the server to check if the user has access to the requested route before it is activated.

How do you handle errors in route guards?

Handling errors in route guards is crucial to provide a good UX and manage scenarios where something goes wrong with the authorization check, data fetching, or any other operations performed in the guards. You can utilize RxJS operators like catchError and throwError to handle errors in observables returned from guard methods.

For example, handling error in Resolve guard:

@Injectable()
export class UserResolver implements Resolve<any> {
    constructor(private userService: UserService, private router: Router) {}

    resolve(route: ActivatedRouteSnapshot): Observable<any> {
        return this.userService.getUser(route.params['userId'])
            .pipe(
                catchError((err) => {
                    if (err.status === 404) {
                        this.router.navigate(['/404']);
                    } else {
                        this.router.navigate(['/error']);
                    }
                    return EMPTY;
                })
            );
    }
}

If an error occurs while fetching user data, it redirects the user to either the '404' or '/error' route and then returns EMPTY, so the route doesn't activate.

Is it possible to combine multiple route guards into one?

While combining multiple route guards into one service isn't directly supported by Angular, you can implement multiple interfaces within the same guard class to combine checks. However, this may make your class large and difficult to maintain, so it's usually better to keep each guard focused on a specific task.

Example of combining CanActivate and CanLoad in a same guard:

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

    canActivate(
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot): boolean {
        // Reuse existing canActivate logic
        return this.checkUserAccess();
    }

    canLoad(route: Route): boolean {
        // Reuse existing canActivate logic to check if the user has access
        return this.checkUserAccess();
    }

    private checkUserAccess(): boolean {
        return this.authService.isAuthenticated();
    }
}

What are the benefits of using Angular Route Resolvers?

Using Angular route resolvers offers numerous benefits including:

  • Pre-fetching Data: Data fetching is performed upfront, so it’s available right when the component initializes, thus speeding up the component rendering process.
  • Cleaner Code: Keeps data fetching logic encapsulated within the resolver, instead of spreading it across various lifecycle hooks or constructors of route components.
  • Automatic Cancellation: Subscriptions created within the resolver are automatically cleaned up when navigation to the route fails (for instance, when a Resolve emits an error or rejects the promise).
  • Improved Testing: Resolvers can be easier to test because they separate the concerns of routing logic and component logic.

Are there any common pitfalls to avoid when using Route Guards and Resolvers?

Certainly, here are some common pitfalls to watch out for when using Angular Route Guards and Resolvers:

  • Not Providing Proper Error Handling: As discussed earlier, errors within guards or resolvers should be properly handled otherwise, unexpected behaviors like route loading failure can lead to application-wide issues.
  • Overusing Route Guards: Applying guards unnecessarily, such as performing authentication checks on every route regardless of access requirements, can cause performance issues and slow down the application.
  • Incorrect Usage of Interfaces: Sometimes, developers may confuse different guard interfaces or their roles which leads to incorrect implementation and can have undesired effects on route navigation.
  • Dependency Injection Issues: If dependencies of your guards aren't properly provided at the application root level (using providedIn: 'root'), Angular will throw runtime errors.
  • Blocking the Main Thread: Performing synchronous operations within your guards (such as blocking API calls) can block the main thread leading to a non-responsive app.

By being aware of these potential pitfalls, you can design more robust and performant Angular applications.

How do I test Angular Route Guards and Resolvers?

Testing Angular route guards and resolvers involves mocking the dependencies (such as AuthService, HttpClient, etc.) to simulate different scenarios.

For example, testing a CanActivate guard:

describe('AuthGuard', () => {
    let guard: AuthGuard;
    let mockAuthService: any;
    let mockRouter: any;

    beforeEach(() => {
        mockAuthService = jasmine.createSpyObj(['isAuthenticated']);
        
        mockRouter = {
            navigate: jasmine.createSpy('navigate')
        };

        TestBed.configureTestingModule({
            providers: [
                AuthGuard,
                { provide: AuthService, useValue: mockAuthService },
                { provide: Router, useValue: mockRouter }
            ]
        });
        guard = TestBed.inject(AuthGuard);
    });

    it('should return true when isAuthenticated returns true', () => {
        mockAuthService.isAuthenticated.and.returnValue(true);

        expect(guard.canActivate({}, {} as RouterStateSnapshot)).toBeTrue();
        expect(mockRouter.navigate).not.toHaveBeenCalled();
    });

    it('should return false and navigate to login page when isAuthenticated returns false', () => {
        mockAuthService.isAuthenticated.and.returnValue(false);

        expect(guard.canActivate({}, {} as RouterStateSnapshot)).toBeFalse();
        expect(mockRouter.navigate).toHaveBeenCalledWith(['/login']);
    });
});

Testing a Resolve service would involve testing the behavior when the data is successfully fetched and when an error occurs during the fetch.

describe('DataResolver', () => {
    let resolver: DataResolver;
    let mockDataService: jasmine.SpyObj<DataService>;

    beforeEach(() => {
        mockDataService = jasmine.createSpyObj('DataService', ['getDataById']);

        TestBed.configureTestingModule({
            providers: [
                DataResolver,
                { provide: DataService, useValue: mockDataService }
            ]
        });
        resolver = TestBed.inject(DataResolver);
    });

    it('should fetch data from data service', () => {
        mockDataService.getDataById.and.returnValue(of({name: 'John Doe', age: 30}));

        resolver.resolve({paramMap: {get: () => '123'}} as ActivatedRouteSnapshot,{url: '/' }as RouterStateSnapshot)
            .subscribe(data => {
                expect(data).toEqual({name: 'John Doe', age: 30});
            });

        expect(mockDataService.getDataById).toHaveBeenCalledWith('123');
    });

    it('should handle errors properly', () => {
        mockDataService.getDataById.and.returnValue(throwError(() => new Error('Failed')));

        expect(() =>
            resolver.resolve({paramMap: {get: () => '123'}} as ActivatedRouteSnapshot,{url: '/'} as RouterStateSnapshot)
                  .subscribe()).toThrow(new Error('Failed'));
    });
});

Testing route guards and resolvers helps ensure that your application behaves correctly across all scenarios, improving stability and reliability.