Skip to main content

Overview

The Odontología Frontend uses Angular’s standalone routing system with a flat, straightforward route configuration. All routes are defined in app.routes.ts and configured through the provideRouter function.

Route Definitions

The application’s complete routing configuration:
src/app/app.routes.ts
import { Routes } from '@angular/router';
import { LoginComponent } from './login/login';
import { HomeComponent } from './home/home';
import { Patient } from './patient/patient';
import { NewPatient } from './new-patient/new-patient';
import { PacienteDetail } from './paciente-detail/paciente-detail';
import { Tratamientos } from './tratamientos/tratamientos';
import { Appointment } from './appointment/appointment';
import { Calendar } from './calendar/calendar';

export const routes: Routes = [
    { path: '', redirectTo: 'login', pathMatch: 'full' },
    { path: 'login', component: LoginComponent, data: { title: 'Acceso' } },
    { path: 'home', component: HomeComponent, data: { title: 'Panel de Control' } },
    { path: 'patient', component: Patient, data: { title: 'Listado de Pacientes' } },
    { path: 'patient/new', component: NewPatient, data: { title: 'Nuevo Paciente' } },
    { path: 'patient/:id', component: PacienteDetail, data: { title: 'Detalle de Paciente' } },
    { path: 'tratamientos', component: Tratamientos, data: { title: 'Gestión de Tratamientos' } },
    { path: 'citas', component: Appointment, data: { title: 'Citas Médicas' } },
    { path: 'calendar', component: Calendar, data: { title: 'Calendario de Citas' } }
];

Route Structure

Root Redirect

{ path: '', redirectTo: 'login', pathMatch: 'full' }
The root path redirects to the login page, making it the default landing page for the application.
The pathMatch: 'full' ensures the redirect only occurs when the path is exactly empty, not when it’s a prefix of another path.

Authentication Routes

{ path: 'login', component: LoginComponent, data: { title: 'Acceso' } }
URL: /loginPurpose: User authentication and access controlComponent: LoginComponent

Main Application Routes

{ path: 'home', component: HomeComponent, data: { title: 'Panel de Control' } }
URL: /homeThe main dashboard showing overview statistics and quick actions.
The application has three patient-related routes:Patient List
{ path: 'patient', component: Patient, data: { title: 'Listado de Pacientes' } }
URL: /patient - Displays all patients with search functionalityNew Patient
{ path: 'patient/new', component: NewPatient, data: { title: 'Nuevo Paciente' } }
URL: /patient/new - Form to register a new patientPatient Detail
{ path: 'patient/:id', component: PacienteDetail, data: { title: 'Detalle de Paciente' } }
URL: /patient/:id - Detailed view of a specific patient
The route order is important: /patient/new must come before /patient/:id to prevent “new” from being interpreted as an ID parameter.
{ path: 'tratamientos', component: Tratamientos, data: { title: 'Gestión de Tratamientos' } }
URL: /tratamientosManages dental treatment types, pricing, and descriptions.
Two routes handle appointment functionality:Appointments List
{ path: 'citas', component: Appointment, data: { title: 'Citas Médicas' } }
URL: /citas - List view of all appointmentsCalendar View
{ path: 'calendar', component: Calendar, data: { title: 'Calendario de Citas' } }
URL: /calendar - Calendar visualization of appointments

Route Data

All routes include a data object with metadata:
data: { title: 'Acceso' }
This data can be accessed by components and services for:
  • Setting page titles
  • Breadcrumb navigation
  • Analytics tracking
  • Access control decisions

Accessing Route Data

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

class SomeComponent {
  constructor(private route: ActivatedRoute) {
    this.route.data.subscribe(data => {
      console.log(data['title']); // Access the title
    });
  }
}

Dynamic Routing

Parameterized Routes

The patient detail route uses URL parameters:
{ path: 'patient/:id', component: PacienteDetail, data: { title: 'Detalle de Paciente' } }
Accessing Route Parameters:
import { ActivatedRoute } from '@angular/router';

class PacienteDetail {
  patientId: number;

  constructor(private route: ActivatedRoute) {
    this.patientId = Number(this.route.snapshot.paramMap.get('id'));
  }
}
Navigation to Parameterized Routes:
<a [routerLink]="['/patient', patient.id]">View Details</a>

Declarative Navigation

Using RouterLink directive in templates:
<a routerLink="/home">Home</a>
<a [routerLink]="['/patient', 123]">Patient 123</a>

Programmatic Navigation

Navigating from component code:
import { Router } from '@angular/router';

class SomeComponent {
  constructor(private router: Router) {}

  goToHome() {
    this.router.navigate(['/home']);
  }

  goToPatient(id: number) {
    this.router.navigate(['/patient', id]);
  }

  goToNewPatient() {
    this.router.navigateByUrl('/patient/new');
  }
}

Router Configuration

The router is configured in the application config:
src/app/app.config.ts
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes)
  ]
};

Router Features

The current configuration uses the default router features. Additional features can be enabled:
import { provideRouter, withDebugTracing, withHashLocation } from '@angular/router';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(
      routes,
      withDebugTracing(),      // Enable console logging of router events
      withHashLocation()       // Use hash-based routing (#/path)
    )
  ]
};

Conditional UI Based on Routes

The root component demonstrates route-based UI control:
src/app/app.ts
import { NavigationEnd, Router } from '@angular/router';
import { toSignal } from '@angular/core/rxjs-interop';
import { filter, map } from 'rxjs';

export class App {
  private readonly router = inject(Router);

  private readonly currentUrl = toSignal(
    this.router.events.pipe(
      filter(event => event instanceof NavigationEnd),
      map(event => (event as NavigationEnd).urlAfterRedirects)
    ),
    { initialValue: this.router.url }
  );

  protected showMenu = computed(() => {
    const url = this.currentUrl();
    return url ? !url.includes('/login') : true;
  });
}
This pattern:
  1. Listens to router events
  2. Filters for NavigationEnd events
  3. Extracts the current URL
  4. Converts the observable to a signal
  5. Computes UI state based on the current route
Template usage:
@if (showMenu()) {
  <app-menu></app-menu>
}
The menu and header are hidden on the login page to provide a focused authentication experience.

Route Guards

While not currently implemented, route guards can be added to protect routes:
import { CanActivateFn } from '@angular/router';

export const authGuard: CanActivateFn = (route, state) => {
  const authService = inject(AuthService);
  
  if (authService.isAuthenticated()) {
    return true;
  }
  
  const router = inject(Router);
  return router.createUrlTree(['/login']);
};

// Apply to routes
export const routes: Routes = [
  { 
    path: 'home', 
    component: HomeComponent,
    canActivate: [authGuard]
  }
];

Best Practices

Use RouterLink

Prefer declarative routerLink over programmatic navigation when possible

Meaningful Paths

Use descriptive, semantic path names that reflect content

Route Data

Leverage route data for metadata and configuration

Lazy Loading

Consider lazy loading for large feature modules

Common Routing Scenarios

export class NewPatient {
  constructor(
    private patientService: PatientService,
    private router: Router
  ) {}

  onSubmit(formData: any) {
    this.patientService.addPatient(formData);
    this.router.navigate(['/patient']);
  }
}
// Navigate to /patient?search=john
this.router.navigate(['/patient'], { 
  queryParams: { search: 'john' } 
});

// Access query parameters
this.route.queryParams.subscribe(params => {
  const search = params['search'];
});

Relative Navigation

// From /patient/123, navigate to /patient/123/edit
this.router.navigate(['edit'], { relativeTo: this.route });

// Navigate to parent: /patient/123 -> /patient
this.router.navigate(['..'], { relativeTo: this.route });

Next Steps

Architecture

Learn about the application’s overall architecture

State Management

Explore how state is managed across routes

Build docs developers (and LLMs) love