Enrutamiento básico

<< Router de Angular Parámetros de entrada >>

 Vamos a crear las rutas de la aplicación. Como todas las rutas están relacionadas con productos, van a compartir el prefijo /products (esta característica la podremos aprovechar más adelante). Las rutas a crear son las siguientes:

  • /products → Cargará el componente products-page
  • /products/add → Cargará el componente product-form
  • /products/:id → Cargará el componente product-detail
  • Ruta vacía '' o incorrecta '**' → Redirigiremos a /products.

La ruta products/add debe ir delante de products/:id. Esto se debe a que el componente 'add' de la ruta es un valor fijo mientras que ':id' es una variable (se representa con dos puntos delante). Si ponemos 'products/:id' primero, al navegar a products/add, cargará product-detail, tomando 'add' como el valor de la id del producto a mostrar. En definitiva, las rutas con componentes cuyo valor es fijo, siempre deben ir delante.

Definir las rutas de la aplicación

Las rutas se definen en el archivo app.routes.ts:

export const routes: Routes = [
  { path: 'products', component: ProductsPageComponent },
  { path: 'products/add', component: ProductFormComponent },
  { path: 'products/:id', component:  ProductDetailComponent},
  { path: '', redirectTo: '/products', pathMatch: 'full' },
  // Aquí podríamos cargar un página de error 404 por ejemplo
  { path: '**', redirectTo: '/products' }
];

Estas rutas están registradas por defecto en el archivo app.config.ts usando la función provideRouter:

//...
import { routes } from './app.routes';
//...
export const appConfig: ApplicationConfig = {
  providers: [/* ... */ provideRouter(routes), /* ... */]
};

También podemos definir la estructura de las rutas en base a una jerarquía. En este caso, las 3 rutas tienen en común el prefijo '/products', por lo que podríamos definir la ruta /products y con la propiedad children configurar el resto de rutas como dependientes de dicho prefijo así:

export const routes: Routes = [
  { path: 'products',
    children: [
       { path: '', component: ProductsPageComponent },
       { path: 'add', component: ProductFormComponent },
       { path: ':id', component: ProductDetailComponent }
    ]
  },
  { path: '', redirectTo: '/products', pathMatch: 'full' },
  { path: '**', redirectTo: '/products' }
];

Activando el router en la plantilla

Para que se carguen los diferentes componentes según la ruta, debemos incluir un componente router-outlet en la plantilla de AppComponent. Dentro de este elemento, el router de Angular cargará las diferentes vistas/componentes. También incluiremos una barra de navegación que nos permita ir a las diferentes rutas.

Para usar router-outlet debes importar el componente RouterOutlet y las directivas que vamos a usar (RouterLink, RouterLinkActive).

Así quedaría AppComponent (ya no necesitamos ProductsPageComponent en el array de imports):

<nav class="navbar navbar-expand navbar-dark bg-dark mb-4">
  <div class="container-fluid">
    <a class="navbar-brand" [routerLink]="['/products']">{{ title }}</a>
    <ul class="navbar-nav me-auto">
      <li class="nav-item">
        <a
          class="nav-link"
          [routerLink]="['/products']"
          [routerLinkActive]="['active']"
          [routerLinkActiveOptions]="{ exact: true }"
        >
          Products
        </a>
      </li>
      <li class="nav-item">
        <a
          class="nav-link"
          [routerLink]="['/products/add']"
          [routerLinkActive]="['active']"
        >
          New product
        </a>
      </li>
    </ul>
  </div>
</nav>

<div class="container">
  <router-outlet></router-outlet>
</div>

Los enlaces utilizan la directiva [routerLink] en lugar del atributo href. Esta directiva puede tomar como valor un string → routerLink="products/13", o pasarle un array con los componentes de la ruta, lo que permite vincular valores a variables/propiedades más fácilmente → [routerLink]="['products', product.id]".

La directiva [routerLinkActive] se utiliza para asignar una o más clases CSS a un elemento cuando esa ruta está activa (de esta manera, podemos ver mejor en qué sección nos encontramos).

El parámetro de entrada [routerLinkActiveOptions] con el atributo exact establecido a true se utiliza en combinación con la directiva routerLinkActive. Por defecto, un enlace está activo cuando la ruta definida es un prefijo de la ruta actual → (/products es un prefijo de /products/23 o /products/34/edit). Con esta opción configurada, el enlace estará activo solo cuando coincida exactamente con la ruta actual.

Ya podemos eliminar el componente product-form de la plantilla de products-page, ya que lo hemos movido a su propia ruta. Ahora se cargará justo debajo de router-outlet, el componente cuya ruta esté activa.

Cambiando el título de la página

El título de la página se encuentra en el archivo index.html. En este archivo es donde se cargan todas las bibliotecas y estilos, así como también el AppComponent. Angular solo tiene control directo sobre lo que sucede dentro de la etiqueta <app-root> (puede tener otro nombre). Por lo tanto, no se puede establecer el valor del elemento <title> con algo como <title>{{title}}</title>.

Para cambiar el título de la página, al navegar a una ruta, se puede añadir una propiedad a la ruta llamada title. Angular establecerá el nuevo título de la página una vez navegue a dicha ruta.

export const routes: Routes = [
  {
    path: 'products',
    component: ProductsPageComponent,
    title: 'Productos | Angular Products',
  },
  {
    path: 'products/add',
    component: ProductFormComponent,
    title: 'Añadir producto | Angular Products',
  },
  //...
];

Si lo queremos cambiar dinámicamente una vez estemos dentro de la ruta. Por ejemplo, poner como título el nombre del producto que se está visualizando, se puede usar el servicio Title de Angular para ello.

export class ProductsPageComponent {
  //...
  #titleService = inject(Title);

  constructor(): void {
    this.#titleService.setTitle("Productos | Angular Products");
    //...
  }
}

Adaptando la página del formulario

Como hemos movido el formulario (product-form) a otra ruta, ya podemos quitar el componente de la página products-page. Ahora al insertar un nuevo producto, simplemente redirigimos a la página de productos y tampoco no necesitaremos resetear el formulario, ni el parámetro de salda (output).

export class ProductFormComponent {
  // Quitamos el ouput 'add'
  newProduct: Product = {
    id: 0,
    description: '',
    available: '',
    imageUrl: '',
    rating: 1,
    price: 0,
  };

  #productsService = inject(ProductsService);
  #router = inject(Router);

  addProduct() { // No necesitamos el formulario (NgForm) para resetearlo
    this.#productsService
      .insertProduct(this.newProduct)
      .subscribe(() => this.#router.navigate(['/products']));
  }
}

<< Router de Angular Parámetros de entrada >>