tutoriales.com

Navegación Intuitiva en Angular: Explorando el Router y sus Funcionalidades Avanzadas

Este tutorial te guiará a través del sistema de enrutamiento de Angular, explorando desde la configuración básica de rutas hasta funcionalidades avanzadas como los Guards, Resolvers y estrategias de precarga. Aprenderás a construir una experiencia de navegación robusta e intuitiva para tus usuarios.

Intermedio25 min de lectura7 views
Reportar error

🚀 Introducción al Angular Router: El Corazón de la Navegación

El enrutador de Angular es una parte fundamental para construir aplicaciones de una sola página (SPA). Permite a los usuarios navegar entre diferentes vistas de la aplicación sin tener que recargar la página completa, ofreciendo una experiencia de usuario fluida y dinámica. En este tutorial, desglosaremos todas las características clave del Angular Router, desde lo más básico hasta las técnicas más avanzadas para que puedas implementar una navegación robusta y eficiente en tus proyectos.

Comenzaremos con la configuración esencial, luego profundizaremos en características como parámetros de ruta, rutas anidadas, protección de rutas con Guards y la precarga de datos para optimizar el rendimiento. ¡Prepárate para llevar la navegación de tu aplicación Angular al siguiente nivel!


🛠️ Configuración Básica del Router

Para empezar a usar el enrutador de Angular, primero necesitas importarlo y configurarlo en tu módulo principal, generalmente AppModule. Angular CLI se encarga de gran parte de esto si generas un proyecto nuevo con --routing, pero es crucial entender cómo funciona.

📦 Importando y Configurando RouterModule

El RouterModule de @angular/router es la clave para habilitar el enrutamiento. Lo importamos en el array imports de nuestro @NgModule y usamos el método forRoot() para las rutas principales de la aplicación. Para módulos feature, usaríamos forChild().

💡 Consejo: Usa `forRoot()` solo una vez en el módulo raíz (`AppModule`). Para módulos de características (lazy loaded), usa `forChild()` para evitar registrar el proveedor del router varias veces.
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ContactComponent } from './contact/contact.component';

const routes: Routes = [
  { path: '', component: HomeComponent }, // Ruta por defecto
  { path: 'about', component: AboutComponent },
  { path: 'contact', component: ContactComponent },
  { path: '**', redirectTo: '' } // Wildcard para rutas no encontradas
];

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

Luego, asegúrate de importar AppRoutingModule en tu AppModule:

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ContactComponent } from './contact/contact.component';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    AboutComponent,
    ContactComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule // Importa tu módulo de enrutamiento
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

📍 El router-outlet y routerLink

Para que Angular sepa dónde renderizar los componentes asociados a las rutas, necesitamos el <router-outlet>. Este es un placeholder donde Angular inserta dinámicamente el componente activo.

Para crear enlaces de navegación, usamos la directiva routerLink en lugar de href para aprovechar el enrutamiento interno de Angular, evitando recargas completas de la página.

<!-- app.component.html -->
<nav>
  <a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Home</a>
  <a routerLink="/about" routerLinkActive="active">About</a>
  <a routerLink="/contact" routerLinkActive="active">Contact</a>
</nav>

<router-outlet></router-outlet>
📌 Nota: `routerLinkActive="active"` añade la clase `active` al elemento cuando la ruta está activa. `[routerLinkActiveOptions]="{exact: true}"` asegura que la clase `active` solo se aplique si la ruta coincide exactamente, útil para la raíz `/`.

🗺️ Rutas con Parámetros y Consultas

Las aplicaciones rara vez tienen rutas estáticas. Necesitamos pasar datos entre rutas para mostrar detalles de elementos o filtrar contenido.

🆔 Parámetros de Ruta

Los parámetros de ruta son segmentos dinámicos en la URL, útiles para identificar un recurso específico (por ejemplo, /products/123).

Definición de ruta:

// app-routing.module.ts (o el módulo de routing relevante)
const routes: Routes = [
  // ... otras rutas
  { path: 'product/:id', component: ProductDetailComponent }
];

Navegación a la ruta:

<!-- en algún lugar, por ejemplo, product-list.component.html -->
<div *ngFor="let product of products">
  <h3>{{ product.name }}</h3>
  <a [routerLink]="['/product', product.id]">Ver Detalles</a>
</div>

Accediendo al parámetro en el componente:

Para acceder al parámetro id dentro de ProductDetailComponent, inyectamos ActivatedRoute.

// product-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-product-detail',
  template: '<h2>Detalle del Producto {{ productId }}</h2>'
})
export class ProductDetailComponent implements OnInit {
  productId: string | null = null;
  private routeSubscription: Subscription | undefined;

  constructor(private route: ActivatedRoute) { }

  ngOnInit(): void {
    // Usando snapshot (solo si el parámetro no cambia en la misma ruta)
    // this.productId = this.route.snapshot.paramMap.get('id');

    // Usando observable (recomendado para parámetros que pueden cambiar)
    this.routeSubscription = this.route.paramMap.subscribe(params => {
      this.productId = params.get('id');
      // Aquí puedes cargar los detalles del producto usando this.productId
      console.log('ID del producto:', this.productId);
    });
  }

  ngOnDestroy(): void {
    if (this.routeSubscription) {
      this.routeSubscription.unsubscribe();
    }
  }
}

❓ Parámetros de Consulta (Query Params)

Los parámetros de consulta son pares clave-valor que aparecen después de ? en la URL (por ejemplo, /products?category=electronics&sort=price). Son útiles para filtros, paginación o cualquier estado que no sea parte de la identidad única de la ruta.

Navegación con Query Params:

// Desde un componente
import { Router } from '@angular/router';

// ...

constructor(private router: Router) { }

navigateToProductsWithFilter() {
  this.router.navigate(['/products'], {
    queryParams: { category: 'electronics', page: 2 },
    queryParamsHandling: 'merge' // Mantiene los query params existentes
  });
}

Accediendo a Query Params en el componente:

// product-list.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-product-list',
  template: `
    <h2>Lista de Productos</h2>
    <p>Categoría: {{ categoryFilter }}</p>
    <p>Página: {{ pageNumber }}</p>
  `
})
export class ProductListComponent implements OnInit {
  categoryFilter: string | null = null;
  pageNumber: string | null = null;
  private queryParamsSubscription: Subscription | undefined;

  constructor(private route: ActivatedRoute) { }

  ngOnInit(): void {
    this.queryParamsSubscription = this.route.queryParams.subscribe(params => {
      this.categoryFilter = params['category'];
      this.pageNumber = params['page'];
      console.log('Filtro de categoría:', this.categoryFilter, 'Página:', this.pageNumber);
    });
  }

  ngOnDestroy(): void {
    if (this.queryParamsSubscription) {
      this.queryParamsSubscription.unsubscribe();
    }
  }
}

🚪 Rutas Anidadas y Auxiliares

🌲 Rutas Anidadas (Child Routes)

Las rutas anidadas permiten que un componente padre tenga sus propias sub-rutas, mostrando diferentes vistas dentro de su <router-outlet>. Esto es ideal para layouts complejos o secciones con varias pestañas.

Definición de rutas anidadas:

// user-routing.module.ts (para un módulo de usuarios, por ejemplo)
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { UserComponent } from './user.component';
import { UserProfileComponent } from './user-profile/user-profile.component';
import { UserSettingsComponent } from './user-settings/user-settings.component';

const userRoutes: Routes = [
  { 
    path: 'users/:id', 
    component: UserComponent, // Componente padre que contendrá el router-outlet
    children: [
      { path: '', redirectTo: 'profile', pathMatch: 'full' }, // Redirección por defecto
      { path: 'profile', component: UserProfileComponent },
      { path: 'settings', component: UserSettingsComponent }
    ]
  }
];

@NgModule({
  imports: [RouterModule.forChild(userRoutes)],
  exports: [RouterModule]
})
export class UserRoutingModule { }

En el UserComponent (user.component.html), necesitas otro <router-outlet>:

<!-- user.component.html -->
<h1>Detalles del Usuario {{ userId }}</h1>
<nav>
  <a [routerLink]="['./profile']" routerLinkActive="active">Perfil</a>
  <a [routerLink]="['./settings']" routerLinkActive="active">Configuración</a>
</nav>
<router-outlet></router-outlet> <!-- Aquí se renderizarán UserProfileComponent o UserSettingsComponent -->

Navegación:

  • /users/123/profile
  • /users/123/settings

🎭 Rutas Auxiliares (Secondary Routes / Named Outlets)

Las rutas auxiliares permiten mostrar múltiples componentes de ruta en paralelo en diferentes <router-outlet>s de la misma página. Son útiles para modales, sidebars o elementos que deben aparecer independientemente de la ruta principal.

Definición de rutas auxiliares:

// app-routing.module.ts
const routes: Routes = [
  // ... rutas principales
  { path: 'chat', component: ChatComponent, outlet: 'sidebar' }, // Outlet con nombre
  { path: 'notifications', component: NotificationsComponent, outlet: 'popup' }
];

Definición de router-outlet con nombre:

<!-- app.component.html -->
<nav>
  <a routerLink="/home">Home</a>
  <a routerLink="/about">About</a>
</nav>
<router-outlet></router-outlet> <!-- Outlet principal -->

<router-outlet name="sidebar"></router-outlet> <!-- Outlet de sidebar -->
<router-outlet name="popup"></router-outlet>   <!-- Outlet de popup -->

Navegación a rutas auxiliares:

<!-- Desde un componente o template -->
<a [routerLink]="[{ outlets: { sidebar: ['chat'] } }]">Abrir Chat</a>
<a [routerLink]="[{ outlets: { popup: ['notifications'] } }]">Ver Notificaciones</a>

<!-- O para cerrar un outlet auxiliar -->
<a [routerLink]="[{ outlets: { sidebar: null } }]">Cerrar Chat</a>
⚠️ Advertencia: Las rutas auxiliares pueden hacer que las URLs sean más complejas. Úsalas con moderación y cuando realmente necesites mostrar contenido *independiente* de la ruta principal.

🛡️ Protegiendo Rutas con Guards

Los Guards son clases que implementan interfaces específicas para decidir si un usuario puede activar una ruta, salir de ella, cargar un módulo o resolver datos. Son esenciales para la seguridad y la experiencia del usuario.

Tipos de Guards

GuardInterfazPropósito
---------
CanActivateCanActivateControla si una ruta puede ser activada.
CanActivateChildCanActivateChildControla si una ruta hija puede ser activada.
---------
CanDeactivateCanDeactivateControla si un usuario puede abandonar una ruta.
CanLoadCanLoadControla si un módulo lazy-loaded puede ser cargado.
---------
CanMatchCanMatchControla si una ruta puede ser matcheada (Angular v13+).

Ejemplo: CanActivate para Autenticación

  1. Crea el Guard:
// auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service'; // Tu servicio de autenticación

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

canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

if (this.authService.isAuthenticated()) {
return true; // El usuario está autenticado, permite el acceso
} else {
// El usuario no está autenticado, redirige a la página de login
return this.router.createUrlTree(['/login']);
// O simplemente this.router.navigate(['/login']); return false;
}
}
}
  1. Aplica el Guard a la ruta:
// app-routing.module.ts
import { AuthGuard } from './auth.guard';
import { AdminDashboardComponent } from './admin-dashboard/admin-dashboard.component';

const routes: Routes = [
// ...
{ 
path: 'admin',
component: AdminDashboardComponent,
canActivate: [AuthGuard] // Protege esta ruta
},
{ path: 'login', component: LoginComponent }
];
🔥 Importante: Los Guards pueden retornar `boolean`, `UrlTree`, `Observable` o `Promise`. Esto permite lógica síncrona, asíncrona o redirigir directamente.

🔄 Resolviendo Datos Antes de la Activación con Resolvers

Los Resolvers permiten precargar datos necesarios para un componente antes de que la ruta sea activada. Esto asegura que el componente siempre tenga los datos listos al inicializarse, evitando pantallas de carga o flashes de contenido.

Ejemplo: ProductResolver

  1. Crea el Resolver:
// product.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable, of } from 'rxjs';
import { ProductService, Product } from './product.service'; // Tu servicio de productos

@Injectable({
providedIn: 'root'
})
export class ProductResolver implements Resolve<Product> {
constructor(private productService: ProductService) {}

resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<Product> | Promise<Product> | Product {
const productId = route.paramMap.get('id');
if (productId) {
return this.productService.getProduct(productId); // Retorna un Observable<Product>
} else {
// Manejar el caso donde no hay ID o un ID inválido
return of(null as any); // Devuelve un observable de null, o lanza un error
}
}
}
  1. Aplica el Resolver a la ruta:
// app-routing.module.ts
import { ProductResolver } from './product.resolver';

const routes: Routes = [
// ...
{ 
path: 'product/:id',
component: ProductDetailComponent,
resolve: {
product: ProductResolver // El nombre 'product' será la clave para acceder a los datos
}
}
];
  1. Accediendo a los datos en el componente:
// product-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Product } from './product.service';

@Component({
selector: 'app-product-detail',
template: `
<h2>{{ product?.name }}</h2>
<p>{{ product?.description }}</p>
<p>Precio: {{ product?.price | currency }}</p>
`
})
export class ProductDetailComponent implements OnInit {
product: Product | undefined;

constructor(private route: ActivatedRoute) { }

ngOnInit(): void {
this.route.data.subscribe(data => {
this.product = data['product']; // Accede a los datos resueltos por su nombre clave
console.log('Producto cargado por resolver:', this.product);
});
}
}
Datos precargados 🚀

⚡ Estrategias de Precarga (Preloading Strategies)

El Lazy Loading (carga perezosa) es genial para reducir el tamaño inicial de la aplicación, pero puede introducir un pequeño retraso cuando el usuario navega a un módulo cargado perezosamente por primera vez. Las estrategias de precarga ayudan a mitigar esto precargando módulos en segundo plano.

Angular proporciona dos estrategias de precarga integradas:

  • NoPreloading: (Por defecto) No precarga ningún módulo lazy-loaded.
  • PreloadAllModules: Precarga todos los módulos lazy-loaded después de que la aplicación haya cargado completamente.

Configurando una Estrategia de Precarga

// app-routing.module.ts
import { RouterModule, Routes, PreloadAllModules } from '@angular/router';
import { AdminModule } from './admin/admin.module'; // Ejemplo de módulo lazy-loaded

const routes: Routes = [
  // ...
  { 
    path: 'admin', 
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) // Ruta lazy-loaded
  }
];

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

Estrategias de Precarga Personalizadas

Si necesitas un control más granular, puedes crear tu propia estrategia de precarga. Esto es útil para precargar solo ciertos módulos o precargar módulos basados en la probabilidad de que un usuario los visite (por ejemplo, después de un login exitoso).

  1. Crea una estrategia de precarga personalizada:
// custom-preloading-strategy.ts
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of, timer } from 'rxjs';
import { Injectable } from '@angular/core';
import { switchMap } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class CustomPreloadingStrategy implements PreloadingStrategy {
preload(route: Route, fn: () => Observable<any>): Observable<any> {
// Si la ruta tiene la propiedad `data` y `preload` es true
if (route.data && route.data['preload']) {
// Precarga el módulo después de un retraso de 5 segundos
return timer(5000).pipe(switchMap(() => fn()));
} else {
// No precarga
return of(null);
}
}
}
  1. Define la propiedad data en la ruta:
// app-routing.module.ts
import { CustomPreloadingStrategy } from './custom-preloading-strategy';

const routes: Routes = [
// ...
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
data: { preload: true } // Marca este módulo para precarga
},
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),
data: { preload: false } // No precargar este módulo
}
];

@NgModule({
imports: [RouterModule.forRoot(routes, { preloadingStrategy: CustomPreloadingStrategy })],
exports: [RouterModule]
})
export class AppRoutingModule { }
💡 Consejo: Considera precargar módulos después de la autenticación del usuario o durante períodos de inactividad para optimizar la experiencia sin sobrecargar el navegador.

🔗 Navegación Programática

Además de routerLink en el template, a menudo necesitarás navegar programáticamente desde el código TypeScript, por ejemplo, después de enviar un formulario, autenticar a un usuario o manejar un evento.

Para ello, inyecta el Router en tu componente o servicio:

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

@Component({
  selector: 'app-some-component',
  template: '<button (click)="goToDashboard()">Ir al Dashboard</button>'
})
export class SomeComponent {
  constructor(private router: Router) { }

  goToDashboard() {
    // Navegación simple
    this.router.navigate(['/dashboard']);

    // Navegación con parámetros de ruta
    this.router.navigate(['/product', '456']);

    // Navegación con parámetros de consulta
    this.router.navigate(['/products'], { queryParams: { category: 'books' } });

    // Navegación con fragmento (hash en la URL)
    this.router.navigate(['/about'], { fragment: 'section-2' });

    // Navegación relativa (útil en rutas anidadas)
    // this.router.navigate(['./profile'], { relativeTo: this.route });
  }
}

El método navigate() devuelve un Promise<boolean> que indica si la navegación fue exitosa. Esto te permite encadenar acciones o manejar errores.


✨ Eventos del Router

El Router emite una serie de eventos durante el ciclo de vida de la navegación. Estos eventos son muy útiles para tareas como mostrar/ocultar un spinner de carga, enviar analíticas o inicializar servicios.

Los eventos son observables y se pueden suscribir a ellos a través de la propiedad events del servicio Router.

`NavigationStart`: La navegación ha comenzado.
`RouteConfigLoadStart`: Un módulo lazy-loaded está a punto de ser cargado.
`RouteConfigLoadEnd`: Un módulo lazy-loaded ha sido cargado.
`RoutesRecognized`: Las rutas han sido reconocidas.
`GuardsCheckStart`: Los Guards están a punto de ser ejecutados.
`ChildActivationStart`: Las rutas hijas están a punto de ser activadas.
`ActivationStart`: Un componente está a punto de ser activado.
`ResolveStart`: Los Resolvers están a punto de ser ejecutados.
`ResolveEnd`: Los Resolvers han terminado de ejecutarse.
`ActivationEnd`: Un componente ha sido activado.
`ChildActivationEnd`: Las rutas hijas han sido activadas.
`GuardsCheckEnd`: Todos los Guards han terminado de ejecutarse.
`NavigationEnd`: La navegación ha terminado con éxito.
`NavigationCancel`: La navegación ha sido cancelada (ej. por un Guard).
`NavigationError`: La navegación falló debido a un error.

Ejemplo: Mostrar un spinner de carga

// app.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, Event, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router';

@Component({
  selector: 'app-root',
  template: `
    <div *ngIf="loading" class="spinner">Cargando...</div>
    <router-outlet></router-outlet>
  `,
  styles: [`
    .spinner {
      position: fixed; top: 0; left: 0; width: 100%; height: 100%;
      background: rgba(0,0,0,0.5); color: white; display: flex;
      justify-content: center; align-items: center; z-index: 9999;
    }
  `]
})
export class AppComponent implements OnInit {
  loading = false;

  constructor(private router: Router) { }

  ngOnInit() {
    this.router.events.subscribe((event: Event) => {
      if (event instanceof NavigationStart) {
        this.loading = true;
      } else if (event instanceof NavigationEnd || event instanceof NavigationCancel || event instanceof NavigationError) {
        this.loading = false;
      }
    });
  }
}
Ciclo de Vida de Navegación en Angular NavigationStart RouteConfigLoadStart / End (Lazy) RoutesRecognized GuardsCheckStart / End ResolveStart / End ActivationStart / End NavigationEnd NavigationCancel NavigationError

⚙️ Configuración Avanzada del Router

El RouterModule.forRoot() acepta un segundo argumento de configuración que permite ajustar el comportamiento del router.

RouterModule.forRoot(routes, {
  // Configuración avanzada aquí
});

Opciones de Configuración Comunes

OpciónDescripción
------
enableTracingDesarrollo: true para imprimir eventos de navegación en la consola del navegador. Útil para depuración.
useHashConfiguración: true para usar rutas basadas en hash (#/ruta). Por defecto es false (rutas HTML5). Si false, requiere configuración del servidor para manejar las URLs.
------
initialNavigationRendimiento: Controla cuándo se realiza la navegación inicial. 'enabled' (por defecto), 'disabled', 'enabledBlocking' (espera a Guards/Resolvers antes de renderizar), 'enabledNonBlocking'.
onSameUrlNavigationUX: 'reload' si el Router debe volver a ejecutar Guards y Resolvers cuando se navega a la misma URL; 'ignore' (por defecto) para ignorar.
------
scrollPositionRestorationUX: Define cómo el Router maneja la posición de scroll. 'disabled' (por defecto), 'top' (scrollea al principio), 'enabled' (restaura la posición previa).
anchorScrollingUX: true para scrollear al fragmento de la URL. Requiere scrollPositionRestoration configurado en 'enabled'.
------
paramsInheritanceStrategyAvanzado: 'emptyOnly' (por defecto) o 'always'. Controla si los parámetros de la ruta padre se heredan a las rutas hijas.
urlUpdateStrategyAvanzado: 'deferred' (por defecto) para actualizar la URL después de la navegación, 'eager' para actualizar la URL al inicio de la navegación.
Ejemplo de Configuración con `enableTracing` y `useHash`
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';

const routes: Routes = [
  { path: '', component: HomeComponent }
];

@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      enableTracing: true,       // Para depuración, registra eventos del router en la consola
      useHash: false,            // Usa rutas HTML5 (ej. /home) en lugar de hash (ej. #/home)
      scrollPositionRestoration: 'enabled', // Restaura la posición de scroll al navegar
      anchorScrolling: 'enabled' // Scrollea a fragmentos de la URL (ej. /page#section)
    })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule { }

🌐 Consideraciones para Despliegue (HTML5 vs Hash Routes)

Cuando utilizas la estrategia de enrutamiento HTML5 (useHash: false, por defecto), la URL en el navegador se ve como una URL de servidor tradicional (ej. midominio.com/productos). Sin embargo, como Angular es una SPA, no hay un archivo productos.html real en el servidor. Si un usuario actualiza la página (F5) o accede directamente a midominio.com/productos, el servidor intentará encontrar ese archivo, resultando en un error 404.

Para solucionar esto, tu servidor debe estar configurado para redirigir todas las solicitudes a tu index.html (o el punto de entrada de tu aplicación Angular), permitiendo que el Router de Angular se encargue de la navegación.

Configuración del Servidor:

  • Apache: Añade un archivo .htaccess en la raíz de tu aplicación:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
  • Nginx: Añade la configuración try_files a tu bloque location:
server {
listen 80;
server_name midominio.com;
root /usr/share/nginx/html; # Ruta a los archivos de tu build de Angular

location / {
try_files $uri $uri/ /index.html;
}
}
  • Node.js (Express): Usa el middleware express.static y luego un fallback para index.html:
const express = require('express');
const path = require('path');
const app = express();

app.use(express.static(path.join(__dirname, 'dist/your-app-name'))); // Servir archivos estáticos

app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/your-app-name/index.html'));
});

const port = process.env.PORT || 8080;
app.listen(port, () => console.log(`Server listening on port ${port}`));

Si la configuración del servidor no es una opción, puedes usar la estrategia HashLocationStrategy (useHash: true), que añade # a la URL (ej. midominio.com/#/productos). Esta estrategia no requiere configuración del servidor, ya que el navegador siempre solicita el index.html y el JavaScript de Angular maneja la navegación después del #.

Comparativa de Enrutamiento en SPAs HTML5 History API midominio.com/perfil Configuración del Servidor Obligatorio: Redirigir todas las rutas 404 hacia index.html Ventajas: • URLs limpias y legibles • Mejor para el SEO (indexable) Desventajas: • Requiere control sobre el server • "Fallo" al refrescar sin config Hash Location Strategy midominio.com/#/perfil Configuración del Servidor No requiere cambios. El servidor ignora todo lo que va tras el #. Ventajas: • Funciona en cualquier hosting • No rompe al recargar la página Desventajas: • URLs menos estéticas (con #) • Limitaciones en SEO antiguo

✅ Conclusión

El Angular Router es una herramienta increíblemente potente y flexible que te permite crear experiencias de navegación ricas e intuitivas en tus aplicaciones web. Desde la configuración básica hasta el uso de Guards para proteger rutas, Resolvers para precargar datos y estrategias de precarga para optimizar el rendimiento, has cubierto una amplia gama de funcionalidades.

Al dominar estas técnicas, podrás construir aplicaciones Angular que no solo sean funcionales, sino también rápidas, seguras y agradables de usar. ¡Experimenta con lo aprendido y sigue explorando la vasta documentación de Angular para descubrir aún más posibilidades!

Tutorial Completo Angular Router Desarrollo Web

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!