¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.dev
Sara López 04/06/2020 Cargando comentarios…
Actualmente se desarrollan aplicaciones cada vez más complejas, con mucho contenido y diferentes pantallas. Estas aplicaciones se crean montando componentes más pequeños y especializados. Angular nos facilita su desarrollo pudiendo separar grandes funcionalidades en componentes pequeños e independientes.
Gracias al uso de componentes se logra un código más legible y mantenible, pero es necesario lograr una eficiente comunicación entre los mismos, ya que se empieza a tener una gran cantidad de componentes a medida que la aplicación crece.
En estos casos, es normal que nos surjan una serie de dudas: ¿estoy separando bien estos componentes?, ¿la comunicación entre ellos es la correcta?, ¿dónde debería tratar los datos?
En este post vamos a hablar sobre el patrón contenedor/presentadores que ayudará a resolver estas preguntas que surgen al desarrollar con este framework basado en componentes.
Es importante conocer cómo se pueden comunicar los componentes entre sí, dependiendo de la situación en la que nos encontremos.
Al desarrollar una nueva pantalla, por lo general utilizamos varios componentes que comparten el mismo contenedor (padre) que facilita la comunicación entre ellos. Para realizar esta comunicación aplicaremos el patrón contenedor/presentadores.
{
path: 'detail/:id',
component: HeroDetailComponent
}
La comunicación entre páginas diferentes se realiza mediante el routing, así pasamos parámetros de una a otra.
//Create observable
const myObs = from ('Hello world!');
//Suscribe observable
const subscription = filteredObs.subscribe(char => console.log(char))
Cuando se necesita comunicar componentes o servicios desacoplados, la comunicación se hace más complicada. Se realiza mediante observables.
Gracias a los observables podemos usar un almacén de datos y, cuando se modifique algún dato de dicho almacén, recibir automáticamente los cambios, sin tener que programar a mano ese tránsito de la información.
La idea principal de este patrón es que la responsabilidad de obtener y manipular el modelo se centralice en el Contenedor. Los Presentadores recibirán la información desde el Contenedor y la presentarán al usuario, esperando su reacción. Cuando ocurra, lo notificarán de vuelta al Contenedor padre que interactúa con el modelo.
Seguir este patrón es bastante sencillo en la práctica, pero requiere un trabajo previo antes de empezar a desarrollar. Hay que estructurar bien la pantalla, identificar qué componentes se van a usar y qué requisitos tienen. Realizar este trabajo previo lleva un tiempo pero ahorrará mucho trabajo en el desarrollo y posibles modificaciones futuras.
En Angular todo son componentes, pero utilizando este patrón hay que diferenciar entre:
Estos componentes principales son los encargados de obtener datos, preparar los datos necesarios para los presentadores, aplicarles lógica de negocio y guardarlos cuando corresponda. Además de esto, el contenedor puede estar preparado para recibir eventos de los presentadores.
Por lo tanto, tendremos una vista muy sencilla y un controlador más complejo. La vista será la composición de los componentes presentadores.
En el caso de tener que interactuar con un servidor, el contenedor será el encargado de llamar al servicio correspondiente. Las llamadas a las APIs nunca se declaran en el componente, siempre se deben realizar a través de un servicio.
La función de estos subcomponentes es mostrar el contenido que le envía el componente principal y controlar la funcionalidad propia del subcomponente. Además, estos subcomponentes pueden estar preparados para recibir parámetros del componente padre que le indican cómo tiene que comportarse.
Es importante entender que estos subcomponentes tienen que ser totalmente independientes y no conocer ninguna lógica de la aplicación, de tal manera que estos podrían ser utilizados en cualquier aplicación sin tener que adaptar nada.
Muy importante no realizar llamadas a servicios en los presentadores ya que de esta manera el componente dejará de cumplir este patrón y no será reutilizable.
La comunicación entre contenedor y presentador se pueden dar en dos sentidos:
Contenedor-Presentador
En este caso el contenedor pasa información al presentador. Esta información no es únicamente contenido que el presentador va a mostrar, sino también configuración que usará el presentador para actuar de una manera determinada.
Para realizar esta comunicación se utiliza el decorador @Input.
Los pasos para realizar esta comunicación en Angular son:
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'app-presentador',
templateUrl: './presentador.component.html',
styleUrls: ['./presentador.component.css']
})
export class PresentadorComponent implements OnInit {
@Input() infoContenedor: string;
constructor() { }
ngOnInit() {
}
}
ngOnInit () {
console.log(this.infoContenedor)
}
<div>
<h3>HTML presentador</h3>
Todo lo que está contenido dentro de este div está declarado en el html correspondiente al elemento
presentador.component.html.
El siguiente contenido está pasado desde el padre, gracias a la declaración @Input.:
<strong>{{infoContenedor}}</strong>
</div>
<app-presentador [infoContenedor]="’string’"></app-presentador>
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-contenedor',
templateUrl: './contenedor.component.html',
styleUrls: ['./contenedor.component.css']
})
export class ContenedorComponent implements OnInit {
datoComunicar: string;
constructor() { }
ngOnInit() {
}
realizarComunicacion(dato: string) {
this.datoComunicar = dato;
}
}
<div class="container">
<h1>HTML Contenedor</h1>
Todo lo que está contenido dentro de este div está declarado en el html correspondiente al elemento
contenedor.component.html
<app-presentador [infoContenedor]="datoComunicar"></app-presentador>
</div>
Presentador-Contenedor
En este caso el presentador pasa información al contenedor. Esta comunicación es muy utilizada cuando se quiere avisar al presentador de alguna acción que ha sucedido.
Para realizar esta comunicación se utiliza el decorador @Output.
Los pasos para realizar esta comunicación en Angular son:
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-presentador',
templateUrl: './presentador.component.html',
styleUrls: ['./presentador.component.css']
})
export class PresentadorComponent implements OnInit {
@Output() eventoComunicar = new EventEmitter();
constructor() { }
ngOnInit() {
}
realizarComunicacion(dato: string){
this.eventoComunicar.emit({elemento: dato});
}
}
<app-presentador
(eventoComunicar)="realizaComunicacionHijo($event)"
>
</app-presentador>
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-contenedor',
templateUrl: './contenedor.component.html',
styleUrls: ['./contenedor.component.css']
})
export class ContenedorComponent implements OnInit {
datoComunicarPadre: string;
constructor() { }
ngOnInit() {
this.realizarComunicacionHijo();
}
realizaComunicacionHijo(event) {
this.datoComunicarPadre = event.elemento;
}
}
Para entender mejor este patrón vamos a ver un ejemplo de cómo se aplica a un caso práctico de uso.
La siguiente pantalla se trata de un listado de personas a las que se le puede enviar una encuesta para que voten sus preferencias. Este listado está ordenado alfabéticamente.
Lo primero es distinguir el contenedor y los presentadores. El contenedor contiene un texto y un listado. El texto no es necesario separarlo en un componente ya que muy simple, pero el listado si lo separaremos en un componente.
Vamos a analizar qué funciones tiene que realizar cada uno:
Contenedor:
Presentador:
Para asegurarse de que estamos definiendo bien los subcomponentes hay que plantearse si este podría ser usado en una aplicación completamente diferente. En nuestro caso se trata de un listado con un botón, pero este no tiene conocimiento de que es lo que está mostrando ni la acción que desempeña el botón.
Además de definir el contenedor y el presentador siguiendo el patrón, hay que definir el servicio que tenemos que utilizar para obtener los amigos y guardar los votos. Ningún componente debe hacer llamadas a las APIs, son los servicios los que se tienen que ocupar de esto. Gracias a esto los componentes son más independientes, que es nuestro objetivo principal.
Código Contenedor
import { Component, OnInit } from '@angular/core';
import { VoteService } from '../vote.service';
@Component({
selector: 'app-vote',
templateUrl: './vote.component.html',
styleUrls: ['./vote.component.css']
})
export class VoteComponent implements OnInit {
constructor (private voteService: VoteService) { }
allFriends: string[];
ngOnInit (): void {
this.getFriends();
}
getFriends () {
this.voteService.getFriends().subscribe(friends => {
this.allFriends = friends;
})
}
sendVote (item) {
this.voteService.saveVote(item).subscribe(friends => {
alert('Guardado correctamente')
})
}
}
<h1>Envia la encuesta para que la voten tus amigos</h1>
<app-list
[items]='allFriends'
[buttonText]="'Enviar'"
(clickItem)="sendVote($event)"
Código Presentador
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent {
constructor () { }
@Input() items: string[];
@Input() buttonText: string;
@Output() clickItem = new EventEmitter;
propagationClick (item) {
this.clickItem.emit(item)
}
}
<ul>
<li *ngFor="let item of items">
<span>{{item}}</span>
<button (click)="propagationClick(item)">{{buttonText}}</button>
</li>
</ul>
Código Servicio
import { Injectable } from '@angular/core';
import { of, Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class VoteService {
constructor () { }
getFriends (): Observable<string[]> {
//Simulación llamada API
const friends = ['Maria', 'Juan', 'Elena', 'Pepe', 'Carlos']
return of(friends)
}
saveVote (vote): Observable<boolean> {
//Simulacion llamda API, devuelve que se ha guardado correctamente el voto
return of(true)
}
}
Como ya sabemos lo más importante para un buen proyecto es seguir una buena arquitectura, así que es importante conocer patrones a seguir que hagan nuestro código más sencillo y legible.
En este caso, hemos visto un ejemplo muy sencillo de cómo aplicar este patrón. Puede parecer que no tiene gran importancia utilizarlo, pero imagina una pantalla con muchas tabs, cada una con sus propios contenidos y llamadas a servicios, siempre será necesario un contenedor que gestione todo el contenido y la lógica de negocio y unos componentes muy independientes que únicamente saben mostrar contenido.
¡Espero haberos animado a seguir haciendo un código limpio y sencillo!
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.