Introducción, qué es un componente

Bien, un concepto importante que tienes que aprender sobre Angular es el concepto de componente. Angular se basa sobre todo en componentes, así que lo primero y fundamental es ver este concepto.

Un componente es una parte de la web que tiene vista, estilos y lógica. Las etiquetas HTML en cierto modo no dejan de ser componentes, por ejemplo, la etiqueta <input> es un componente porque, de forma predeterminada, se pinta con unos estilos y con la lógica necesaria para que el usuario pueda hacer clic y escribir en ellos.

En otras palabras, con los componentes web puedes crear tus propias etiquetas HTML que podrás reusar en toda la página web. En Angular y en otros frameworks los componentes suelen recibir una propiedad o varias que sirven de entrada para la configuración de ese componente (por ejemplo a los inputs le puedes pasar el tipo: text, password, email…).

Los componentes de Angular van más allá y pueden incluso recibir objetos javascript completos y arrays para su configuración. Por ejemplo puedes crear un componente para pintar una lista de usuarios. Este componente recibirá como entrada un array de usuarios a pintar. De esta forma consigues en puedas reutilizar esta lista en otra parte de la web, por lo que es fundamental que los hagas lo más genéricos posibles para que se puedan reusar.

En Angular los componentes tienen un archivo por cada parte del componente, es decir, se suele crear un archivo .html para la vista, otro .ts (typescript) para la lógica del componente y otro .css o .scss para los estilos del componente.

Dentro de la lógica del componente, en archivo .ts, es el lugar en el que se define el funcionamiento de ese componente. Aquí se definen las variables, se crean los métodos y se recogen y preparan los datos para pasarlos a la vista.

Dentro de los componentes también se pueden llamar o hacer uso de otros componentes, por lo que al final se suelen tener estructuras con forma de árbol.

Por ejemplo una estructura de componentes con componentes padres e hijos puede ser la siguiente:

Un componente que tiene dos componentes hijos y a su vez, su hijo tiene otros dos hijos

Creando componentes en Angular

Vamos ahora con la creación de componentes en Angular. Manos a la obra.

Antes de nada te recomiendo que ya tengas creado un proyecto de Angular. Si no sabes cómo instalar Angular y cómo crear un proyecto te recomiendo que mires el artículo anterior: Guía de instalación de Angular

Existen dos maneras de hacerlo: la método automático y el manual.

Método automático

Si estamos usando Angular CLI, en nuestro proyecto, existe un comando muy útil para generar la estructura básica de un componente.

Por ejemplo, imaginemos que queremos crear un componente que sirva para crear el menú principal de una web y que lo queremos llamar navbar por el momento. Tan solo tienes que ejecutar en la terminal:

ng generate component navbar

La última palabra es el nombre del componente. Si es un componente de varias palabras yo recomiendo que le des un nombre en kebab-case, es decir, separada cada palabra por un guión, por ejemplo: user-list.

Si navegamos ahora a la carpeta app del proyecto veremos que Angular CLI ha creado por nosotros una carpeta llamada navbar. Dentro de la carpeta navbar se ha creado un archivo css, un archivo html y un archivo .ts (controlador) junto con un .spec.ts (este archivo es para los tests, no te preocupes de momento).

Otro detalle a tener en cuenta es que Angular ha importado por nosotros el nuevo componente en el archivo app.module.ts. El archivo app.module.ts sirve para declarar los componentes a nivel global para poder reutilizarlos más tarde.

Método manual

Si no estás usando Angular CLI, o no quieres usarlo, tendrás que crear los archivos que genera el comando a mano.

Lo único que tienes que hacer es generar una nueva carpeta dentro de la carpeta app con el nombre del componente que quieras. Dentro de la carpeta creas un archivo que se llame nombre-componente.component.ts, por ejemplo navbar.component.ts.

Dentro de ese componente escribes esta estructura:

// app/navbar/navbar.component.ts

import { Component } from "@angular/core";

@Component({
  selector: "app-navbar",
  templateUrl: "./navbar.component.html",
  styleUrls: ["./navbar.component.css"]
})
export class NavbarComponent {}

Para que no salte error también tendrás que crear el archivo nombre-componente.component.html y nombre-componente.component.css, por el momento vacíos. Para el ejemplo del componente de navbar lo que hago es crear: navbar.component.html y navbar.component.css.

Por último tendrás que importar el componente dentro del archivo app.module.ts que como hemos dicho se encarga de definir los componentes que puedes usar. Para nuestro ejemplo:

// app.module.ts

import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";

import { AppComponent } from "./app.component";
import { NavbarComponent } from "./navbar/navbar.component";

@NgModule({
  declarations: [AppComponent, NavbarComponent],
  imports: [BrowserModule],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

Estructura básica de los componentes

Ahora que ya tenemos el componente creado vamos a echar un vistazo a lo que tiene de forma básica.

Como hemos dicho antes, un componente en Angular debería tener 3 archivos, uno .html, otro .ts y otro .css o .scss.

Los archivos html y css no tienen mucho misterio de moment y los podemos tener vacíos. Dentro del .html puedes escribir código HTML como haces normalmente (más adelante veremos cómo pintar variables del componente, condicionales y bucles) y dentro del .css o .scss se escriben los estilos que va a tener ese componente.

OJO a esto porque es importante. Los estilos CSS que apliques en ese archivo solo afectarán a cómo se vea ese componente y no afectará a otros componentes. Esto permite que los estilos no se peguen entre sí por lo que no pasa nada que haya dos componentes con la misma clase CSS.

Veamos ahora el archivo .ts también llamado controlador que es el que define la lógica del componente. La estructura básica ya la has visto, pero por si acaso es esta:

// app/navbar/navbar.component.ts

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

@Component({
  selector: 'app-navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.css']
})

export class NavbarComponent  {

  constructor() { }

}

Lo primero que se hace es importar Component de @angular/core. Esto sirve para poder invocar justo debajo @Component, una especie de método que recibe un objeto con la configuración del componente. Dentro de esa configuración hay tres propiedades:

  • selector: El selector es el nombre que va a tener la etiqueta HTML que sirve para poder usar este componente, para este ejemplo del navbar será <app-navbar></app-navbar>, es decir, desde el HTML de cualquier otro componente poniendo esa etiqueta se pintará el navbar. A

    Angular tiene una convención de nombre para el selector, kebab-case (el nombre de los selectores tiene que ser una palabra seguida de un guión y otra palabra: app-ejemplo).

  • templateUrl: La ruta al fichero .html de ese componente para crear la vista.

  • styleUrl: La ruta al fichero .html de ese componente para crear la vista.

Lo normal es que estás rutas empiecen por ./ (carpeta actual) ya que normalmente los ficheros html y css se encuentran en la misma carpeta que el fichero .ts.

Por último hacemos export class con el nombre del componente (sin guiones aquí). Dentro del export se crea el método constructor (vacío por el momento).

¡OJO!. El nombre que usas cuando exportas el componente (NavbarComponent en el ejemplo de arriba) es el que luego tienes que poner entr llaves para importar el componente dentro del app.module, en este caso:

import { NavbarComponent } from "./navbar/navbar.component";

Cómo usar los componentes dentro de otros

Si ya has creado el componente (puedes añadir alguna etiqueta HTML en el .html) y si ya lo tienes importado correctamente en el app.module.ts, puedes probar que el componente funciona correctamente simplemente creando etiqueta dentro del archivo app.component.html.

En mi caso he borrado todo el contenido del archivo y he puesto:

<!-- app.component.html -->
<div>
  <app-navbar></app-navbar>
</div>

Si el archivo .html del componente que has creado tiene algún contenido podrás verlo si te abres la página en el navegador (localhost:4200).

Cómo crear rutas en Angular. Sistema de routing

Con esto sabemos crear componentes sueltos (todavía sin funcionalidad). Ahora lo que vamos a hacer es asignar ciertos componentes (no es necesario que sean todos) a ciertas rutas de la web.

Por ejemplo, imagina que quieres crear la página /users. Pues lo primero que tienes que hacer es crear el componente UsersComponent (o como quieras llamarlo) como te enseñado más arriba. Por ejemplo:

// app/users/users.component.ts

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

@Component({
  selector: 'app-users',
  templateUrl: './users.component.html',
  styleUrls: ['./users.component.css']
})

export class UsersComponent  {

  constructor() { }

}

Para asignar este componente a una ruta necesitamos hacer uso del routing de Angular, necesitamos el archivo con las rutas.

Creamos un archivo al mismo nivel que el app.module.ts y lo llamamos app.routing.ts.

Una vez creado importamos las rutas de Angular:

import { RouterModule, Routes } from '@angular/router';

Ahora, creamos una variable que guardará la lista de todas las rutas de la web:

const appRoutes = [
  { path: '', component: UsersComponent,  pathMatch: 'full'}
];
  • path: La ruta a que queremos configurar
  • component: Componente asociado a esa ruta. Para que funcione tienes que importar el componente en la parte de arriba, por ejemplo:

    import { UsersComponent } from './users/users.component';
    
  • pathMatch: Esto es opcional, significa que toda la ruta URL tiene que coincidir (no solo cierta parte).

El fichero completo, quedaría de la siguiente manera:

// app.routing.ts
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { UsersComponent } from './users/users.component';

const appRoutes = [
  { path: 'users', component: UsersComponent,  pathMatch: 'full'},
];
export const routing = RouterModule.forRoot(appRoutes);

Solo con esa ruta. Ahora, tenemos que importar las rutas en el archivo app.module.ts, para ello importamos la ruta y lo añadimos, esta vez en la parte de imports:

import { routing } from './app.routing';

...

  imports: [
    BrowserModule,
    routing
  ],

Si tienes la web ejecutándose con ng serve (para el comando y vuelve a arrancarlo para que pille la nueva ruta) y abres la url localhost:4200/users verás que se renderiza el contenido del HTML del componente de Users.

Creando rutas con partes dinámicas

Ahora, imaginemos que queremos crear una página para mostrar en detalle items de una lista. Por ejemplo queremos tener /item/1, /item/2, /item/3, etc. En lugar de crear cada una de esas rutas, puedes configurar que cierta parte de la ruta sea dinámica, es decir, que Angular genere todas las rutas por tí. Para ello tienes que poner dentro del path, a continuación de la barra /, dos puntos y nombre que quieras para esa variable:

const appRoutes = [
 { path: 'items', component: ItemListComponent,  pathMatch: 'full'},
 { path: 'item/:id', component: ItemDetailComponent }
];

Siempre y cuando hayas creado dos nuevos componentes, ItemListComponent y ItemDetailComponent (o como quieras llamarlos).

Con esto se crean todas las rutas posibles que empiecen por /item. El nombre id sive para guardar en esa variable la ruta específica en la que estamos, es decir, luego dentro del componente ItemDetailComponent puedes recoger ese valor para saber en qué ruta estás. Para ello dentro del constructor:

// item-detail.component.ts

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

...

constructor(private route: ActivatedRoute) {
  console.log(route.snapshot.params['id'];)
}

Recuerda importar ActivatedRoute e inyectarlo dentro del constructor con el nombre que quieras.

Se imprimirá por consola el valor que aparece en la ruta, es decir, si navegamos a la ruta /item/2, se imprimirá 2 y si navegamos a /item/foo se imprimirá foo.

¿Qué es el Router outlet de Angular?

Si pruebas las páginas con estos cambios te darás cuenta de que todavía no se muestran las nuevas rutas, esto pasa el primer componente que se carga siempre es app.component.html y como ese archivo ya tiene un HTML es el que se pintará.

IMPORTANTE: Por el momento (luego se puede cambiar) todas las rutas son hijas de app.component.

Dentro del app.component.html tenemos que incluir una etiqueta especial de Angular:

<!-- app.component.html -->

<router-outlet></router-outlet>

router-outlet es una etiqueta especial en Angular que sirve para mostrar los componentes hijos de un componente. Por defecto todos los componentes son hijos del componente AppComponent, por lo que si incluímos esta etiqueta dentro de la vista de AppComponent, se renderizará cada uno de los componentes del routing dependiendo de la página en la que nos encontremos.

Se imprimirá por consola el valor que aparece en la ruta, es decir, si navegamos a la ruta /items, se sustituirá la etiqieta router-outlet por el componente que esté asignado en ela archivo de rutas para ese componente.

Con esto por ejemplo puedes hacer que el componente app-component.html tenga el navbar para que así se pinte en todas las rutas (siempre que exista el conponente de navbar y lo hayas importado en el app.module.ts):

<!-- navbar.component.html -->

<app-navbar></app-navbar>
<router-outlet></router-outlet>

Componentes hijos

Veamos ahora como crear rutas hijas. Esto es muy útil por lo que hemos hablado antes, puedes definir una ruta que tenga una o varias rutas hijas, así consigues que el HTML que pongas en el componente padre se rendericé también en cada una de las rutas hijas. Veamos un eejemplo:

// app.routing.ts

const appRoutes = [
  {
    path: 'admin', component: AdminComponent,
    children: [
      { path: '', component: AdminMainComponent, },
      { path: 'settings', component: SettingsComponent }          
    ]
  }
];

En este ejemplo, la página de admin tiene dos componentes hijos. Para ello se crea la propiedad children dentro del objeto con la ruta que será un array de rutas.

En la ruta /admin/ se cargará el componente MainComponent y en la ruta /admin/settings, el componente SettingsComponent

Los componentes hijos se dibujarán en el router outlet que coloquemos en el padre, es decir para que se renderizen estos componentes, tenemos que poner en la vista del AdminComponent un router-outlet. T

odo lo que incluyamos en la vista del AdminComponent se visualizará también en las dos páginas hijas.

Ya no podemos usar el atributo href, de no ser que queramos navegar a una página fuera de la web.

Para poner un link ahora tenemos que usar esta etiqueta especial de Angular:

  <a routerLink="/list" routerLinkActive="active">Link</a>

De esta manera, por ejemplo, navegaremos a la url /list, y si la tenemos configurada en el app.routing.ts se cargará su vista dentro del router-outlet del componente padre de esa ruta.

Conclusiones

Con esto ya sabemos cómo crear componentes y como asignarlos a rutas, aunque ésto es solo una parte de todo lo que se puede hacer con rutas y componentes.

Lo que yo recomiendo es separar cada cosa en varios componentes, por ejemplo, un componente para mostrar la cabecera de una página, otro para mostrar toda la lista de elementos (en verde), otro componente por cada categoría de la lista (en rojo) y otro por cada item de la lista (azul oscuro). Para páginas pequeñas esta estructura es más tediosa de programar pero para páginas más grandes es la mejor estructura ya que al estar todo dividido en componentes es más fácil de mantener ya que cada cosa funciona independiente.

Una página que tiene un componente de cabecera, otro para mostrar todos los elementos, y un componente por cada elemento de la lista

Si quieres obtener más información sobre el routing o sobre otros aspectos de Angular, visita la documentación oficial de Angular (en inglés):

https://angular.io/guide/router