Curso de Angular: Cómo Hacer una Aplicación Navegable

Tras el anterior tutorial de este curso (en el que vimos «Cómo Crear tu Primera Aplicación en Angular»), en éste nos centraremos en dotar de navegabilidad a nuestra página. Para ello, utilizaremos las «bondades» del propio Angular, ya que gracias a su arquitectura basada en componentes, podremos recargar solo las partes de las páginas que nos interesen, dejando el resto fijas (con las mejoras en eficiencia que ello supone). No obstante, antes de «meternos en harina», es necesario tener en cuenta algunos conceptos previos que pasamos a ver en los siguientes apartados.

Qué es un Componente en Angular

Antes de aprender cómo crear un página navegable en Angular, hemos de conocer el elemento clave de cualquier aplicación: el componente. Considerando el componente principal que se genera automáticamente cuando se crea una aplicación Angular, podemos decir que un componente en Angular es un elemento que está compuesto principalmente por:
  • Un archivo HTML («app.component.html»): es el que se va a visualizar en la interfaz de usuario, la vista o (en términos más simples) lo que vas a ver en la página.
  • Un archivo TS («app.component.ts»): incluye la lógica del componente, en código Typescript, con una clase que contendrá las propiedades que se van a usar en la vista (HTML), junto con los método o acciones que se ejecutarán en la vista.
  • Un archivo CSS («app.component.css»): donde incluiremos los estilos del componente, lo que nos permitirá hacer «bonita» nuestra aplicación.

Hay otros archivos, pero digamos que estos 3 son los principales que forman el «esqueleto» de un componente Angular. De forma análoga a este componente principal que mencionábamos, en Angular podemos crear tantos componentes como sean necesarios, dependiendo del tamaño de nuestra aplicación y de la granularidad que queramos darle a cada una de nuestras secciones (o de cómo queramos organizarlas).

Componente de Angular

Qué es un Decorador

Si vamos a un término muy técnico, un decorador es una implementación de un patrón de diseño de software que permite extender una función dentro de otra función, sin modificar la original de la que se está extendiendo. En términos simples, un decorador nos permite decorar una función a la cual deseamos especificarle unos metadatos, dentro de los cuales se informa sobre la función y sus comportamientos.

Decorador Angular

En Angular, los decoradores son funciones especiales que se utilizan para agregar metadatos a una clase, propiedad o método. Los decoradores se colocan justo encima de la declaración de la clase, propiedad o método que se desea decorar, y se denotan con el símbolo «@» seguido del nombre del decorador. Para utilizar un decorador en Angular, primero debemos importarlo desde el paquete adecuado. Luego, podemos aplicar el decorador justo encima de la declaración de la clase, propiedad o método que se desea decorar.

Decoradores en Angular

Angular tiene varios decoradores predefinidos que se utilizan con frecuencia. Estos son algunos de los decoradores más comunes:

  • @Component: se utiliza para definir un componente en Angular. Se utiliza para especificar la plantilla, estilos y metadatos asociados con el componente.
  • @Directive: se utiliza para definir una directiva personalizada en Angular. Las directivas se utilizan para agregar comportamientos específicos a elementos de la interfaz de usuario.
  • @NgModule: se utiliza para definir un módulo en Angular. Los módulos se utilizan para agrupar componentes, directivas, servicios y otros objetos relacionados entre sí.
  • @Pipe: se utiliza para definir una tubería en Angular. Las tuberías se utilizan para transformar datos en tiempo real en una plantilla.
  • @Injectable: se utiliza para definir un servicio en Angular. Los servicios son una forma de compartir datos y funcionalidades entre componentes.
  • @Input: se utiliza para definir una propiedad de entrada en un componente. Las propiedades de entrada se utilizan para pasar datos a un componente desde un componente padre.
  • @Output: se utiliza para definir un evento en un componente. Los eventos se utilizan para enviar datos desde un componente hijo a un componente padre.
  • @HostBinding: se utiliza para enlazar una propiedad de un componente a una propiedad del elemento DOM que lo contiene.
  • @HostListener: se utiliza para escuchar eventos del elemento DOM que contiene el componente.
  • @ContentChild: se utiliza para obtener una referencia al primer elemento secundario coincidente de un componente.
  • @ContentChildren: se utiliza para obtener una referencia a todos los elementos secundarios coincidentes de un componente.
  • @ViewChild: se utiliza para obtener una referencia al primer elemento hijo coincidente de un componente.
  • @ViewChildren: se utiliza para obtener una referencia a todos los elementos hijos coincidentes de un componente.
  • @Attribute: se utiliza para obtener el valor de un atributo en un elemento DOM.
  • @Self: se utiliza para indicar que una inyección de dependencia debe ser resuelta solo en el propio componente.
  • @Optional: se utiliza para indicar que una inyección de dependencia es opcional y no causará un error si no se puede resolver.
  • @SkipSelf: se utiliza para indicar que una inyección de dependencia debe ser resuelta por un componente superior en la jerarquía de componentes.
  • @Inject: se utiliza para especificar un proveedor de inyección de dependencia personalizado en un componente.

Cómo crear una Página Navegable en Angular

Una vez que sabemos lo que es un componente y lo que es un decorador, ya no nos pillarán de sopetón algunos aspectos técnicos necesarios para incorporar la navegabilidad en una aplicación Angular. ¡Veamos pasito a pasito cómo hacerlo!

Paso 1: Crear el proyecto

Este paso es común a la hora de crear cualquier aplicación. De forma análoga a como hicimos en el tutorial anterior con el famoso «HolaMundo», en este caso crearemos una aplicación que se llamará «PaginaNavegable», ejecutando el siguiente comando:

ng new PaginaNavegable –routing –skip-git –style css

Con el parámetro «–routing» le indicamos a Angular que cree un módulo de enrutamiento (clave en la funcionalidad que pretendemos probar). Por su parte, con el parámetro «–skip-git» indicamos que no se inicialice un repositorio git, y con «– style css» le indicamos la extensión de los ficheros de estilos (si no lo ponemos, el sistema nos lo preguntará).

Creación de proyecto Angular desde un terminal de Visual Studio Code

Con esto ya tendríamos nuestra aplicación creada. Para arrancarla, recordemos que tendríamos que situarnos dentro del directorio «PaginaNavegable» creado y ejecutar el siguiente comando:

ng serve –open

Esto nos permitiría ver la aplicación que crea Angular por defecto. Sin embargo, al igual que en el tutorial anterior, borraremos todo el contenido del fichero «app.component.html» y empezaremos desde cero (al borrar este contenido, recordemos que la aplicación mostrará una página en blanco).

Paso 2: Crear los componentes necesarios

Crearemos una aplicación muy sencillita que estará formada por 3 componentes: «home», «product-list» y «product-detail». Voy a dejar los nombres en inglés porque es la manera en la que aparecen en la mayoría de los tutoriales, pero evidentemente estos componentes también podrían haberse llamado «inicio», «lista-productos» y «detalle-producto» (a gusto del consumidor). Para crear el componente «home», desde un terminal de Visual Studio Code y dentro del directorio de nuestro proyecto, ejecutaríamos:

ng generate component home

Para tenerlo todo mejor organizado, los otros dos componentes los crearemos dentro de un directorio «products», ejecutando el mismo comando e indicando en este caso «products/product-list» y «products/product-detail» respectivamente.

ng generate component products/product-list
ng generate component products/product-detail

Como vemos en la siguiente imagen, Angular nos ha creado carpetas para cada uno de los 3 componentes indicados, con archivos análogos a los que teníamos para el componente principal de la aplicación (que, si recordamos, es «app.component»).

Creación de componentes Angular con "ng generate component"

Una vez hecho esto, automáticamente los componentes creados han sido añadidos al módulo principal de la aplicación. Podemos verlo dentro del archivo «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 { ProductListComponent } from ‘./products/product-list/product-list.component’;
import { ProductDetailComponent } from ‘./products/product-detail/product-detail.component’;
@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    ProductListComponent,
    ProductDetailComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Paso 3: Componente «home»

Para este componente no nos complicaremos la vida. Simplemente crearemos una variable «welcomeText» dentro de su archivo Typescript y mostraremos su valor en el archivo HTML. Dicho lo cual, el archivo «home.component.ts» quedaría de la siguiente forma:

import { Component } from ‘@angular/core’;
@Component({
  selector: ‘app-home’,
  templateUrl: ‘./home.component.html’,
  styleUrls: [‘./home.component.css’]
})
export class HomeComponent {
    welcomeText: string = ‘Página navegable muy sencilla para ver un listado de productos y poder acceder al detalle de cada uno de ellos.’;
}

En cuanto al archivo «home.component.html», pondremos un encabezado «h1» y mostraremos el valor de la variable «welcomeText».

<h1>Home</h1>
<p>{{welcomeText}}</p>

Paso 4: Componente «product-list»

Este componente mostrará un listado de 3 productos («Producto 1», «Producto 2» y «Producto 3») en una lista no ordenada, tal que al pulsar sobre el enlace correspondiente a cada producto el sistema nos redirigirá al componente «product-detail». El archivo Typescript («product-list-component.ts») no requiere ningún cambio, así que quedaría:

import { Component } from ‘@angular/core’;
@Component({
  selector: ‘app-product-list’,
  templateUrl: ‘./product-list.component.html’,
  styleUrls: [‘./product-list.component.css’]
})
export class ProductListComponent {
}

En cuanto al archivo HTML («product-list.component.ts»), incorporaremos el código necesario para situar la lista de productos mencionada.

<h1>Lista de Productos</h1>
<ul class=’nav navbar-nav’>
    <li><a [routerLink]=»[‘/products/1’]»>Producto 1</a></li>
    <li><a [routerLink]=»[‘/products/2’]»>Producto 2</a></li>
    <li><a [routerLink]=»[‘/products/3’]»>Producto 3</a></li>
</ul>

Como vemos, se utiliza una propiedad denominada «routerLink», que es el selector para la directiva RouterLink que convierte los clics del usuario en navegaciones del enrutador.

Paso 5: Componente «product-detail»

Para el detalle de un producto, lo primero que debemos hacer es definir las propiedades que tendrá un producto en sí. Para ello, dentro del directorio «products» crearemos un archivo «product.ts» con el siguiente contenido:

export interface IProduct {
    productId: number;
    productName: string;
    productCode: string;
    releaseDate: string;
    price: number;
    description: string;
    starRating: number;
    imageUrl: string;
  }

Una vez definida la interfaz de nuestro objeto, lo siguiente que debemos hacer es implementar la lógica de nuestro componente en el archivo Typescript («product-detail.component.ts»). El planteamiento es crear 3 productos con datos inventados y, dependiendo del identificador que nos llegue en la url de invocación, mostrar la información de uno u otro. Más adelante, veremos cómo recuperar la información de un producto de una BBDD, pero para este tutorial así es más que suficiente.

Vamos a crear en primer lugar los 3 productos. Dentro de la clase «ProductDetailComponent» incorporaríamos:

product1: IProduct = {
      productId: 1,
      productName: ‘Nombre1’,
      productCode: ‘1’,
      releaseDate: ‘1’,
      price: 1,
      description: ‘1’,
      starRating: 1,
      imageUrl: ‘1’,
    }
    product2: IProduct = {
      productId: 2,
      productName: ‘Nombre2’,
      productCode: ‘2’,
      releaseDate: ‘2’,
      price: 2,
      description: ‘2’,
      starRating: 2,
      imageUrl: ‘2’,
    }
    product3: IProduct = {
      productId: 3,
      productName: ‘Nombre3’,
      productCode: ‘3’,
      releaseDate: ‘3’,
      price: 3,
      description: ‘3’,
      starRating: 3,
      imageUrl: ‘3’,
    }

El compilador de Angular dará un error al no encontrarse el tipo «IProduct», así que tendremos que importarlo a la clase situando al principio del archivo la sentencia «import { IProduct } from ‘../products’;». El siguiente paso sería crear la variable relativa al producto que mostraremos en el HTML, y que llamaremos «product». Para ello, en la clase «ProductDetailComponent» declararíamos:

product: IProduct | undefined;

¡Y ahora llega la parte más entretenida! Una vez creadas las variables, vamos a implementar la lógica en sí. El objetivo es decirle a nuestro componente que, al iniciarse, obtenga el valor del parámetro «id» y actualice el valor de la variable «product» con un producto u otro dependiendo del valor de dicho «id». Para ello, la clase «ProductDetailComponent» implementará «OnInit» («export class ProductDetailComponent implements OnInit»), y dentro de ella crearemos la siguiente función:

ngOnInit():void {
      const id = Number(this.route.snapshot.paramMap.get(‘id’));
      if (id == 1) {
        this.product = this.product1;
      }
      else if (id == 2) {
        this.product = this.product2;
      }
      else if (id == 3) {
        this.product = this.product3;
      }
    }
}

Por último, para que funcione «this.route.snapshot.paramMap.get(‘id’)», hemos de importar la clave ActivatedRoute («import { ActivatedRoute } from ‘@angular/router’;») y añadir un parámetro «route» del tipo anterior en el constructor de la clase («constructor(private route: ActivatedRoute) {}»). Todo esto puede resultar muy confuso escrito así, por lo que a continuación mostraremos el resultado final del archivo:

import { Component, OnInit } from ‘@angular/core’;
import { IProduct } from ‘../products’;
import { ActivatedRoute } from ‘@angular/router’;
@Component({
  selector: ‘app-product-detail’,
  templateUrl: ‘./product-detail.component.html’,
  styleUrls: [‘./product-detail.component.css’]
})
export class ProductDetailComponent implements OnInit {
    pageTitle: string = ‘Product Detail’;
    product: IProduct | undefined;
    product1: IProduct = {
      productId: 1,
      productName: ‘Nombre1’,
      productCode: ‘1’,
      releaseDate: ‘1’,
      price: 1,
      description: ‘1’,
      starRating: 1,
      imageUrl: ‘1’,
    }
    product2: IProduct = {
      productId: 2,
      productName: ‘Nombre2’,
      productCode: ‘2’,
      releaseDate: ‘2’,
      price: 2,
      description: ‘2’,
      starRating: 2,
      imageUrl: ‘2’,
    }
    product3: IProduct = {
      productId: 3,
      productName: ‘Nombre3’,
      productCode: ‘3’,
      releaseDate: ‘3’,
      price: 3,
      description: ‘3’,
      starRating: 3,
      imageUrl: ‘3’,
    }
    constructor(private route: ActivatedRoute) {}
    ngOnInit():void {
      const id = Number(this.route.snapshot.paramMap.get(‘id’));
      if (id == 1) {
        this.product = this.product1;
      }
      else if (id == 2) {
        this.product = this.product2;
      }
      else if (id == 3) {
        this.product = this.product3;
      }
    }
}

Y ya estaría lo más complicado. En el archivo «product.detail.html» simplemente mostraríamos los campos de la variable «product». En este caso, he añadido la directiva «ngIf» de Angular, para verificar que la variable está definida, aunque en nuestro caso no sería necesario.

<h1>Detalle de Producto</h1>
<div *ngIf=»product»>
    <h2>{{product.productName}}</h2>
    <ul>
        <li>productId: {{product.productId}}</li>
        <li>productName: {{product.productName}}</li>
        <li>productCode: {{product.productCode}}</li>
        <li>releaseDate: {{product.releaseDate}}</li>
        <li>price: {{product.price}}</li>
        <li>description: {{product.description}}</li>
        <li>starRating: {{product.starRating}}</li>
        <li>imageUrl: {{product.imageUrl}}</li>
    </ul>
</div>
<div><a [routerLink]=»[‘/products/’]»>Volver al listado de productos</a></div>

El «div» que aparece en la parte aparece en la parte inferior corresponde al típico enlace de «volver a la página anterior», y que, como veremos, suele ser bastante útil para mejorar la navegabilidad de nuestra página.

Paso 6: Implementar la navegabilidad

Una vez creados los componentes necesarios de nuestra aplicación, para implementar finalmente la navegabilidad hemos de modificar el comportamiento del componente principal. En primer lugar, añadiremos simplemente una variable «pageTitle» con el valor «Página Navegable» en el archivo «app.component.ts»:

import { Component } from ‘@angular/core’;
@Component({
  selector: ‘app-root’,
  templateUrl: ‘./app.component.html’,
  styleUrls: [‘./app.component.css’]
})
export class AppComponent {
  pageTitle: string = ‘Página Navegable’;
}

Después, modificaremos el HTML principal para que muestre una parte común (un listado de enlaces) y una parte dinámica (que será la que se recargará en la página cada vez que pulsamos sobre un enlace). Nuestro archivo «app.component.html» tendrá el siguiente aspecto:

<div>
    <a>{{pageTitle}}</a>
    <ul>
        <li><a [routerLink]=»[‘/home’]»>Home</a></li>
        <li><a [routerLink]=»[‘/products’]»>Product List</a></li>
    </ul>
</div>
<router-outlet></router-outlet>

La parte de abajo (donde pone «<router-outlet></router-outlet>») es la parte dinámica que mencionábamos. Por último, hemos de modificar el archivo «app.module.ts» para activar el routing. Para ello, primero hay que importar el servicio que nos ofrece angular para el enrutamiento, lo que nos permitirá usar las directivas «[routerLink]» y «<router-outlet></router-outlet>» en cada componente que está declarado en este módulo.

import { RouterModule } from ‘@angular/router’;
@NgModule({
  declarations: [
    …
  ],
  imports: [
    …
    RouterModule
  ]

De esta manera, también se logra exponer las rutas configuradas, es decir, validar si están disponibles en nuestra aplicación, y en caso de que estén usarlas para navegar. Lo que haremos será usar esa validación pasándole las rutas mediante un array, incluyendo tantos objetos como rutas tengamos:

 RouterModule.forRoot(  [
      { path: ‘products’, component: ProductListComponent },
      { path: ‘products/:id’, component: ProductDetailComponent },
      { path: ‘home’, component: HomeComponent },
      { path: », redirectTo: ‘home’, pathMatch: ‘full’ },
      { path: ‘**’, redirectTo: ‘home’, pathMatch: ‘full’ }
    ])

La clave «path» indica el segmento de url que se pone al final de la misma, indicándose después el componente a cargar. Para el resto, se indica que la aplicación redirigirá a la home. Todo junto quedaría:

import { NgModule } from ‘@angular/core’;
import { BrowserModule } from ‘@angular/platform-browser’;
import { RouterModule } from ‘@angular/router’;
import { AppRoutingModule } from ‘./app-routing.module’;
import { AppComponent } from ‘./app.component’;
import { ProductDetailComponent } from ‘./products/product-detail/product-detail.component’;
import { ProductListComponent } from ‘./products/product-list/product-list.component’;
import { HomeComponent } from ‘./home/home.component’;
@NgModule({
  declarations: [
    AppComponent,
    ProductDetailComponent,
    ProductListComponent,
    HomeComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    RouterModule.forRoot(  [
      { path: ‘products’, component: ProductListComponent },
      { path: ‘products/:id’, component: ProductDetailComponent },
      { path: ‘home’, component: HomeComponent },
      { path: », redirectTo: ‘home’, pathMatch: ‘full’ },
      { path: ‘**’, redirectTo: ‘home’, pathMatch: ‘full’ }
    ])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Paso 7: Testeo de la aplicación

En primer lugar, si accedemos a la URL «http://localhost:4200/» vemos que la aplicación redirige a la «home», tal y como habíamos configurado en nuestro array de enrutamiento:

Página home de aplicación Angular

Si en el listado de arriba pulsamos sobre «Home», permaneceremos en la misma página (o, para ser más correctos, en el mismo componente). En cambio, al pulsar sobre «Product List» se cargará el componente «product-list» (listado de productos):

Página de listado de productos en aplicación Angular

Por último, al pulsar sobre cualquiera de los productos del listado, veremos que el sistema recuperará los datos correspondientes a través de la lógica que implementamos en el componente «product-detail», mostrándose por pantalla:

Página de detalle de producto en aplicación Angular

Además, el enlace añadido en la parte inferior permitirá volver al listado de productos, y podremos ir saltando de un punto a otro de la aplicación tantas veces como queramos, manteniéndose el listado de enlaces superior inamovible. Ojo, que ya sé que este listado de enlaces es muy «feo», y que lo suyo sería que fuera un menú desplegable (o algo por el estilo), pero ya llegaremos al punto de dejar nuestra aplicación «bonita» más adelante en este curso.

Paso 8: Ver la aplicación funcionando

Puedes ver la aplicación funcionando y con toda la navegabilidad en plenas condiciones en la siguiente URL:

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *


error: