ANGULAR: Comunicación de componentes
Angular, Frontend
3 diciembre, 2017
Uno de los detalles más importantes cuando trabajamos con un framework como Angular es conseguir que los diferentes componentes que forman la aplicación sean pequeños y reutilizables. Cuando un componente empieza a tener demasiadas líneas de código o a encargarse de varias operaciones con poca relación entre sí debemos tratar de reducirlo y separar su comportamiento en otros componentes más pequeños.
Sin embargo, esto no siempre es sencillo, porque pasamos de tener todos los datos y referencias almacenados en un único componente, que nos permite trabajar con ellos como nos plazca, a dividirlos en varios componentes que pueden tener dificultades para comunicarse. Esto es precisamente lo que vamos a ver en este artículo, diferentes maneras que tienen nuestros componentes para interactuar entre ellos.
Qué vamos a ver
Vamos a trabajar en un ejemplo sencillo para ver las siguientes opciones que tenemos con Angular:
- Enviar datos desde el componente padre al hijo
- Modificar datos del padre desde el hijo mediante un evento
- Invocar a una función del hijo desde el padre
Para ello el ejemplo va a consistir en lo siguiente: el componente padre contiene un número al que llamaremos ‘total’. El componente hijo consistirá en un campo de tipo numérico que permitirá sumar o restar su contenido al total. Además, el padre indicará a cada uno de los componentes hijos si la operación que ellos realizan será una suma o una resta. Por último, para ver cómo invocar a una función del hijo desde el padre, el padre podrá reiniciar el valor almacenado en el hijo mediante un método reset.
Vamos a ir viéndolo poco a poco para que quede más claro.
Enviar datos desde el componente padre al hijo
En primer lugar creamos nuestro componente hijo. Este va a consistir en un input numérico que nos permitirá indicar el valor, y un botón que será el que más adelante lance la acción para que se sume o se reste al total. Como queremos que el mismo componente sirva tanto para sumar como para restar, permitiremos que reciba un parámetro sum
que le indique si va a realizar una suma o no. Este el archivo de nuestro componente:
1 2 3 4 5 6 7 8 9 10 11 12 |
import {Component, Input} from '@angular/core'; @Component({ selector: 'app-operator', templateUrl: './operator.component.html', styleUrls: ['./operator.component.css'] }) export class OperatorComponent { @Input() sum = true; inputValue = 0; } |
Únicamente definimos en él dos propiedades. En primer lugar, el valor que queremos que reciba del padre, que indicará si es una suma, o de lo contrario, es una resta cuando el valor sea false
. Por defecto tendrá el valor true
. Presta atención en que antes de definir la propiedad hemos usado el decorador @Input()
de Angular. Esto es importante porque es lo que indica a Angular que esa propiedad debe recogerla de los datos que proporcione el componente padre al invocar a este componente desde la plantilla.
La otra propiedad simplemente la usaremos para almacenar el valor del campo numérico, ya que más adelante necesitaremos trabajar con él.
La plantilla de este componente es la siguiente:
1 2 3 4 |
<p> <input type="number" [(ngModel)]="inputValue" min="0" name="inputValue"/> <button>{{sum ? 'Sumar' : 'Restar'}}</button> </p> |
Está compuesta por el campo numérico, al que bindeamos el valor que hemos definido previamente en la plantilla, y además posee un botón, que muestra un texto en función de si realizará una suma o una resta.
[(ngModel)]
debes añadir antes FormsModule
al array imports de tu módulo principal.A continuación pasamos al componente padre. Este en principio simplemente definirá el valor del contador. En su plantilla, incluirá dos veces al componente hijo que hemos creado antes, uno para sumar y otro para restar.
1 2 3 4 5 6 7 8 9 10 11 12 |
import {Component} from '@angular/core'; @Component({ selector: 'app-counter', templateUrl: './counter.component.html', styleUrls: ['./counter.component.css'] }) export class CounterComponent { total = 0; } |
1 2 3 4 5 6 7 |
<div> <h1><strong>Total:</strong> {{total}}</h1> <app-operator></app-operator> <app-operator [sum]="false"></app-operator> </div> |
El primer componente que añade no tiene indicado si es una suma o no, pero como su valor por defecto era true
, actuará como suma. El segundo si se define como una resta, ya que se le indica que no es una suma y nuestro ejemplo solo tiene esas dos opciones.
Y con esto ya hemos visto el primer paso más sencillo, enviar datos desde el padre al hijo.
Modificar datos del padre desde el hijo mediante un evento
Ahora pasamos al siguiente paso, queremos que nuestros componentes hijos sean capaces de actuar sobre el valor que almacena el componente padre, ya que ese es su propósito.
Vamos al componente hijo y añadimos una nueva propiedad.
1 |
@Output() operate = new EventEmitter<number>(); |
Fíjate en que ahora el decorador que utilizamos es @Output()
. Con ello indicamos a Angular que esta propiedad va a emitir en vez de recibir. Como es un valor numérico, añadimos <number>
para tiparlo.
A continuación, creamos la función que se encargará de emitir el valor del campo:
1 2 3 4 |
onClick() { const total = (this.sum) ? this.total : -(this.total); this.operate.emit(total); } |
Esta función simplemente establece como positivo o negativo el valor que teníamos almacenado en función del campo sum
, y después lo emite utilizando el EventEmitter
que habíamos definido.
Este componente ya es capaz de hacer su parte, pero ahora el componente padre debe hacer la suya. Para ello, primero va a definir la función que realmente modifique el campo total
que almacena el valor del contador:
1 2 3 |
onOperate(number: number) { this.total += number; } |
Tan simple como que únicamente recoge el valor que se le envía y lo añade al total. Ahora solo falta un paso: el padre debe indicar al hijo que esta es la función que debe invocar enviándole el valor necesario. Volvemos a la plantilla del padre y bindeamos el manejador del evento:
1 2 3 4 5 6 7 |
<div> <h1><strong>Total:</strong> {{total}}</h1> <app-operator (operate)="onOperate($event)"></app-operator> <app-operator (operate)="onOperate($event)" [sum]="false"></app-operator> </div> |
A la izquierda, el valor de la propiedad en el hijo, a la derecha, la función que ejecutará el padre y que recibe el evento que se ha producido. Con esto ya tenemos funcionando nuestro ejemplo, podemos introducir diferentes valores y sumarlos o restarlos al total.
Invocar a una función del hijo desde el padre
Ya solo nos falta un paso. El hijo tendrá una función reset()
para poner el valor de su <input>
a cero, pero sin embargo su propia plantilla no tiene un botón que invoque a esta función. Añadiremos en el padre botones que nos permitan invocar a esta función en cada componente.
La función reset del hijo es tan simple como esta:
1 2 3 |
reset() { this.total = 0; } |
Y para invocarla, en el padre crearemos un botón para cada componente, quedando con este resultado:
1 2 3 4 5 6 7 8 9 |
<div> <h1><strong>Total:</strong> {{total}}</h1> <app-operator (operate)="onOperate($event)" #sumOperator></app-operator> <button (click)="sumOperator.reset()">Reinicia la suma</button> <app-operator (operate)="onOperate($event)" [sum]="false" #substractOperator></app-operator> <button (click)="substractOperator.reset()">Reinicia la resta</button> </div> |
Como se puede observar, en capa componente que hace de operador, hemos añadido una referencia mediante #
. Gracias a ella, podemos llamar a su método reset()
desde el botón que lo acompaña.
Y esto es todo. Hemos visto como pasar datos como propiedades del nuevo componente y cómo invocar a una función del hijo desde el padre y viceversa, para que podamos comunicar nuestros diferentes componentes en Angular. Puedes ver el ejemplo en funcionamiento aquí: ejemplo
Si tienes alguna duda o sugerencia, no dudes en utilizar los comentarios, estaré encantado de responderte!
Deja un comentario