Skip to main content

Routing Overview

The Trippins frontend uses Angular Router for client-side navigation with route guards for authentication and authorization. All routes are prefixed with /new/ for API versioning compatibility.

Public Routes

Accessible to all users

Protected Routes

Require authentication

Admin Routes

Require ROLE_ADMIN

Guards

Route protection logic

Route Configuration

The main routing module defines all application routes:
src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { RoomComponent } from './components/room/room.component';
import { AboutComponent } from './components/about/about.component';
import { AdminComponent } from './components/admin/admin.component';
import { ErrorComponent } from './components/error/error.component';
import { IndexComponent } from './components/index/index.component';
import { LoginComponent } from './components/login/login.component';
import { NewhotelComponent } from './components/newhotel/newhotel.component';
import { ProfileComponent } from './components/profile/profile.component';
import { RegisterComponent } from './components/register/register.component';
import { RoomDetailsComponent } from './components/room-details/room-details.component';
import { authGuard } from './guards/auth.guard';
import { roleGuard } from './guards/role.guard';

const routes: Routes = [
  { path: '', component: IndexComponent },
  { path: 'new/home', redirectTo: '', pathMatch: 'full' },
  { path: 'new/index', redirectTo: '', pathMatch: 'full' },
  
  // Public routes
  { path: 'new/about', component: AboutComponent },
  { path: 'new/login', component: LoginComponent },
  { path: 'new/register', component: RegisterComponent },
  { path: 'new/rooms', component: RoomComponent },
  { path: 'new/rooms/:id', component: RoomDetailsComponent, canActivate: [authGuard] },
  
  // Protected user routes
  { 
    path: 'new/profile', 
    component: ProfileComponent,
    canActivate: [authGuard]
  },
  
  // Admin routes
  { 
    path: 'new/admin', 
    component: AdminComponent,
    canActivate: [authGuard, roleGuard],
    data: { roles: ["ROLE_ADMIN"] }
  },
  { 
    path: 'new/housing/creation', 
    component: NewhotelComponent,
    canActivate: [authGuard]
  },
  
  // Error route
  { path: 'new/error', component: ErrorComponent },
  
  // Wildcard route (must be last)
  { path: '**', redirectTo: 'error' }
];

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

Route Types

Public Routes

Accessible without authentication:
PathComponentDescription
/IndexComponentLanding page
/new/homeRedirect to /Home alias
/new/aboutAboutComponentAbout page
/new/loginLoginComponentLogin form
/new/registerRegisterComponentRegistration form
/new/roomsRoomComponentHotel listings
Multiple paths can redirect to the same component using redirectTo with pathMatch: 'full' for exact matching.

Protected Routes (Authentication Required)

Require user to be logged in via authGuard:
PathComponentGuardDescription
/new/rooms/:idRoomDetailsComponentauthGuardIndividual room details
/new/profileProfileComponentauthGuardUser profile
/new/housing/creationNewhotelComponentauthGuardCreate hotel listing

Admin Routes (Role-Based Access)

Require both authentication AND admin role:
PathComponentGuardsRequired RoleDescription
/new/adminAdminComponentauthGuard, roleGuardROLE_ADMINAdmin dashboard
Route data can pass configuration to guards. The admin route passes data: { roles: ["ROLE_ADMIN"] } to the roleGuard.

Route Parameters

Path Parameters

Extract dynamic segments from the URL:
{
  path: 'new/rooms/:id',
  component: RoomDetailsComponent,
  canActivate: [authGuard]
}

Query Parameters

Handle optional URL parameters:
this.router.navigate(['new/login'], {
  queryParams: { returnUrl: '/new/profile' }
});
// Results in: /new/login?returnUrl=%2Fnew%2Fprofile
Query parameters are useful for return URLs after login, allowing users to continue where they left off.

Route Guards

Authentication Guard

Functional guard that checks if user is logged in:
src/app/guards/auth.guard.ts
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router';
import { AuthService } from '../services/auth.service';

export const authGuard: CanActivateFn = (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot
) => {
  const authService = inject(AuthService);
  const router = inject(Router);

  if (authService.isLoggedIn()) {
    return true;
  }

  // Redirect to login page with return URL
  return router.createUrlTree(['new/login'], {
    queryParams: { returnUrl: state.url }
  });
};
  1. Inject Dependencies: Uses Angular’s inject() function to get services
  2. Check Authentication: Calls authService.isLoggedIn() to verify JWT token exists
  3. Allow Access: Returns true if authenticated
  4. Redirect: Returns a UrlTree to redirect unauthenticated users to login
  5. Preserve Destination: Adds returnUrl query parameter for post-login redirect

Role-Based Guard

Checks if user has required roles for admin access:
src/app/guards/role.guard.ts
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { inject } from '@angular/core';

export const roleGuard: CanActivateFn = (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot
) => {
  const authService = inject(AuthService);
  const router = inject(Router);

  const requiredRoles = route.data['roles'] as string[];
  if (authService.hasAnyRole(requiredRoles)) {
    return true;
  }
  
  return router.createUrlTree(['new/error']);
};
  1. Extract Required Roles: Gets roles from route data configuration
  2. Check User Roles: Calls authService.hasAnyRole(requiredRoles)
  3. Allow Access: Returns true if user has at least one required role
  4. Deny Access: Redirects to error page if user lacks permissions

Guard Execution Order

When multiple guards are applied, they execute in array order:
{
  path: 'new/admin',
  component: AdminComponent,
  canActivate: [authGuard, roleGuard], // Executes authGuard first, then roleGuard
  data: { roles: ["ROLE_ADMIN"] }
}
1

authGuard Runs First

Verifies user is logged in
2

If Auth Passes, roleGuard Runs

Checks if user has required role
3

Both Must Pass

User must be authenticated AND have admin role

Programmatic Navigation

import { Router } from '@angular/router';

export class HeaderComponent {
  constructor(private router: Router) {}

  logout(): void {
    this.authService.logout();
    this.router.navigate(['new/login']);
  }
}

Template Navigation

<a routerLink="/new/about">About Us</a>
<a [routerLink]="['/new/rooms']">View Rooms</a>
routerLinkActive automatically adds CSS classes when the route is active, useful for navigation highlighting.

Route Events

Listen to navigation events for loading indicators or analytics:
import { Router, NavigationStart, NavigationEnd, NavigationError } from '@angular/router';

export class AppComponent implements OnInit {
  constructor(private router: Router) {}

  ngOnInit() {
    this.router.events.subscribe(event => {
      if (event instanceof NavigationStart) {
        // Show loading spinner
        console.log('Navigation started:', event.url);
      }
      
      if (event instanceof NavigationEnd) {
        // Hide loading spinner
        console.log('Navigation completed:', event.urlAfterRedirects);
      }
      
      if (event instanceof NavigationError) {
        // Handle navigation error
        console.error('Navigation error:', event.error);
      }
    });
  }
}

Wildcard Route

Catch-all route for 404 errors (must be last):
{ path: '**', redirectTo: 'error' }
The wildcard route must be the last route in the configuration. Routes are evaluated in order, so ** will match everything if placed earlier.

Route Resolution

Route Resolvers

Pre-fetch data before navigating to a route (not currently implemented, but recommended pattern):
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { HousingServiceService } from '../services/housing-service.service';

export const roomResolver: ResolveFn<HousingDTO> = (route, state) => {
  const housingService = inject(HousingServiceService);
  const id = Number(route.paramMap.get('id'));
  return housingService.getSpecificRoom(id);
};
{
  path: 'new/rooms/:id',
  component: RoomDetailsComponent,
  resolve: { room: roomResolver },
  canActivate: [authGuard]
}
Resolvers ensure data is loaded before the component renders, preventing blank screens during data fetching.

Authentication Flow with Routing

1

User Attempts Protected Route

User navigates to /new/profile
2

authGuard Executes

Guard checks if JWT token exists in localStorage
3

Not Authenticated

User redirected to /new/login?returnUrl=/new/profile
4

User Logs In

LoginComponent submits credentials to backend
5

Token Stored

AuthService stores JWT token and roles
6

Redirect to Original URL

User navigated back to /new/profile
Login Component Flow
onSubmit(): void {
  this.authService.login({ email, password }).subscribe({
    next: () => {
      // Extract return URL from query params
      const returnUrl = this.router.parseUrl(this.router.url)
        .queryParams['returnUrl'] || '/';
      this.router.navigateByUrl(returnUrl);
    }
  });
}

Admin Authorization Flow

1

User Attempts Admin Route

User navigates to /new/admin
2

authGuard Executes

Verifies user is logged in (has JWT)
3

roleGuard Executes

Checks if user has ROLE_ADMIN in roles array
4

Access Granted or Denied

Admin users see dashboard; others redirected to /new/error
Role Guard Logic
const requiredRoles = route.data['roles'] as string[]; // ["ROLE_ADMIN"]
if (authService.hasAnyRole(requiredRoles)) {
  return true; // User has admin role
}
return router.createUrlTree(['new/error']); // Access denied
For better performance, implement lazy loading for feature modules:
const routes: Routes = [
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
    canActivate: [authGuard, roleGuard],
    data: { roles: ["ROLE_ADMIN"] }
  }
];
Lazy loading splits code into separate bundles that are loaded on-demand, reducing initial page load time.

Route Configuration Best Practices

  • Group related routes together
  • Use consistent naming conventions
  • Document route purposes with comments
  • Keep wildcard route last
  • Apply guards at the highest applicable level
  • Combine guards for layered security
  • Use route data for guard configuration
  • Handle unauthorized access gracefully
  • Implement lazy loading for large features
  • Use route resolvers to pre-fetch data
  • Avoid excessive route changes
  • Consider route reuse strategies

Testing Routes and Guards

Testing Guards

import { TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { authGuard } from './auth.guard';
import { AuthService } from '../services/auth.service';

describe('authGuard', () => {
  let authService: jasmine.SpyObj<AuthService>;
  let router: jasmine.SpyObj<Router>;

  beforeEach(() => {
    const authServiceSpy = jasmine.createSpyObj('AuthService', ['isLoggedIn']);
    const routerSpy = jasmine.createSpyObj('Router', ['createUrlTree']);

    TestBed.configureTestingModule({
      providers: [
        { provide: AuthService, useValue: authServiceSpy },
        { provide: Router, useValue: routerSpy }
      ]
    });

    authService = TestBed.inject(AuthService) as jasmine.SpyObj<AuthService>;
    router = TestBed.inject(Router) as jasmine.SpyObj<Router>;
  });

  it('should allow access when user is logged in', () => {
    authService.isLoggedIn.and.returnValue(true);
    const result = authGuard(null as any, { url: '/new/profile' } as any);
    expect(result).toBe(true);
  });

  it('should redirect to login when user is not logged in', () => {
    authService.isLoggedIn.and.returnValue(false);
    authGuard(null as any, { url: '/new/profile' } as any);
    expect(router.createUrlTree).toHaveBeenCalledWith(
      ['new/login'],
      { queryParams: { returnUrl: '/new/profile' } }
    );
  });
});

Common Routing Patterns

import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { filter } from 'rxjs/operators';

export class BreadcrumbComponent implements OnInit {
  breadcrumbs: Array<{ label: string; url: string }> = [];

  constructor(private router: Router, private route: ActivatedRoute) {}

  ngOnInit() {
    this.router.events
      .pipe(filter(event => event instanceof NavigationEnd))
      .subscribe(() => {
        this.breadcrumbs = this.createBreadcrumbs(this.route.root);
      });
  }

  private createBreadcrumbs(route: ActivatedRoute, url: string = '', breadcrumbs: any[] = []): any[] {
    const children: ActivatedRoute[] = route.children;

    if (children.length === 0) {
      return breadcrumbs;
    }

    for (const child of children) {
      const routeURL: string = child.snapshot.url.map(segment => segment.path).join('/');
      if (routeURL !== '') {
        url += `/${routeURL}`;
      }

      const label = child.snapshot.data['breadcrumb'];
      if (label) {
        breadcrumbs.push({ label, url });
      }

      return this.createBreadcrumbs(child, url, breadcrumbs);
    }

    return breadcrumbs;
  }
}

Next Steps

Angular Services

Explore HTTP services and business logic

Component Architecture

Learn about component structure

Build docs developers (and LLMs) love