Comunicación entre componentes con @Input

  • Por
Interacción entre componentes de Angular, enviando datos a través de propiedades de entrada, definidas con el decorador @Input.

En artículos anteriores del Manual de Angular, al abordar la introducción al framework, hemos mencionado en varias ocasiones una de sus características fundamentales: la arquitectura de componentes.

Hemos dicho que una aplicación en Angular la forma un árbol de componentes, capaces de colaborar entre sí para resolver las necesidades del negocio. Pero lo cierto es que, hasta el momento nuestros componentes han funcionado de manera bastante autónoma, sin la posibilidad de interaccionar los unos con los otros.

En este artículo vamos a comenzar con la revisión de los procesos de comunicación habituales entre los componentes de aplicaciones, analizando especialmente el uso de las propiedades de entrada decoradas con @Input().

Envío de datos entre componentes

Es verdad que en pasados artículos hemos visto como un servicio es capaz de facilitar que los componentes compartan datos, pero no es la única manera que podemos conseguir ese objetivo.

Además de los servicios como comunicadores de datos, existen diferentes escenarios mediante los cuales los componentes pueden compartir información. Atendiendo a la estructura de árbol de componentes en la aplicación, esa comunicación puede surgir de los padres a los hijos y de los hijos hacia los padres.

Paso de datos de padres a hijos

El paso de información de padres a hijos se realiza por medio de propiedades del componente. La información se bindea desde el template, usando los atributos de la etiqueta del componente.

Para ello, las propiedades del componente se deben decorar con @Input, de modo que Angular sea capaz de saber que éstas son capaces de inicializarse, o modificarse, desde fuera. Este es el escenario que vamos a abordar en este artículo.

Paso de datos de los hijos a los padres

También puede surgir la necesidad de que los hijos comuniquen a los padres cambios en la información que ellos manejan. En estos casos Angular utiliza eventos. Es decir, cuando el hijo tiene un dato y quiere hacerlo llegar al padre, genera un evento que puede ser capturado en el padre para realizar aquellas acciones que sean necesarias.

Para definir los eventos que van de los hijos a los padres, Angular usa el decorador @Output, que veremos con detalle en el siguiente artículo.

Nota: El viaje de los datos de los padres a los hijos por medio de sus propiedades y la escalación de eventos para comunicar los datos de hijos a padres tiene el nombre de patrón "mediator".

Paso de datos desde componentes padre a componentes hijo

Ahora vamos a ver cómo se realiza el envío de datos del padre al hijo, comenzando por la definición del dato en el template.

Vamos a suponer que tenemos un componente que se llama VisualizarClienteComponent. Este componente muestra la ficha de un cliente, con sus datos principales (nombre, CIF...). Para funcionar, este componente debe recibir del padre los datos del cliente que debe mostrar.

Por otra parte, tenemos otro componente que usa a VisualizarClienteComponent, que le tiene que pasar los datos que debe mostrar. Al usar el componente, tiene que pasar los datos por medio del template, usando el binding de propiedades.

El uso del componente sería como este:

<dw-visualizar-cliente
  [nombre]="cliente.nombre"
  [cif]="cliente.cif"
  [direccion]="cliente.direccion"
></dw-visualizar-cliente>

Como puedes ver, las propiedades se pasan por medio de binding, usando la sintaxis de los corchetes.

A la vista de este código se entiende que el componente padre tendrá un objeto cliente. El componente hijo tendrá tres propiedades, "nombre", "cif" y "direccion". A la propiedad nombre le pasaremos el valor de "cliente.nombre", a la propiedad "cif" le pasaremos el valor de "cliente.cif", etc.

Nota: nada impide haber pasado el objeto entero, de una sola vez en una única propiedad, en lugar de sus valores por separado como en el ejemplo. Sería incluso más claro y hasta preferible en muchos casos en una aplicación. Sin embargo, a fines didácticos he preferido separar los datos en varias propiedades, por el motivo de poder expresar con más claridad qué pertenece a cada componente, entre hijo y padre.

Declaración de propiedades de entrada con @Input

A continuación vamos a ver cómo definir en el componente hijo las propiedades que son capaces de cargar datos desde el padre. Para ello usaremos el decorador @Input.

Importar el decorador @Input

Como un primer paso en el componente hijo necesitamos hacer el import del decorador Input, que está en "@angular/core".

import { Component, OnInit, Input } from '@angular/core';

Declarar las propiedades con su decorador @Input

También en el código TypeScript del componente hijo, tenemos que declarar las propiedades en la clase, igual que veníamos haciendo, solo que agregaremos el decorador @Input para indicar que esta propiedad se puede manipular desde el padre.

De momento vamos a dejar el decorador vacío, que será suficiente en la mayoría de los casos, aunque podríamos indicar algunas cuestiones como el nombre con el que conocerá la propiedad desde fuera.

@Input()
nombre: string;

Si se desea, es posible aplicar un valor predeterminado a la propiedad, asignando un valor cualquiera. En ese caso, si no se entrega ese valor al usar el componente, se asumirá el definido de manera predeterminada.

@Input()
nombre = 'DesarrolloWeb.com';
Nota: En este caso no es necesario que indiques que el tipo de la propiedad es "string", ya que el compilador de TypeScript es capaz de inferirlo en función del dato asignado.

Código completo del componente

Aparte de los @Input, el código del componente no tiene nada que no se conozca hasta el momento. No obstante dejamos el código completo a continuación.

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'dw-visualizar-cliente',
  templateUrl: './visualizar-cliente.component.html',
  styleUrls: ['./visualizar-cliente.component.css']
})
export class VisualizarClienteComponent implements OnInit {

  @Input()
  nombre = 'DesarrolloWeb.com';
  @Input()
  cif: string;
  @Input()
  direccion: string;

  constructor() { }

  ngOnInit() {
  }

}

El template del componente tampoco tiene ningún detalle a resaltar, puesto que las propiedades se usan igual que hemos visto en anteriores ocasiones.

<p>
  Nombre: {{nombre}}
  <br>
  Cif: {{cif}}
  <br>
  direccion: {{direccion}}
</p>

Binding de una dirección

Estos datos, tal como se ha dicho, viajan del padre al hijo. Si el padre modifica el valor enviado a las propiedades, también se modificará en el hijo. Lo puedes probar si en el template donde has usado el componente pones un botón para que modifique una de sus propiedades.

<button (click)="cliente.cif='ESB9922'">Cambiar CIF</button>

Al pulsar el botón se modificará el CIF y por tanto cambiará el componente que consumía este dato.

Recuerda que el binding es de una sola dirección, como todos los binding de propiedad expresados con los corchetes. Por tanto, si el hijo modifica la información de la propiedad, el nuevo valor no viajará hacia el padre.

En el siguiente artículo te mostraremos cómo hacer que un dato pueda viajar desde el hijo al padre, usando @Output.