Cómo compartir datos entre unos componentes y otros, a fin de facilitar la interoperabilidad, por medio de mecanismos de binding, en AngularJS 1.5.
En los últimos artículos del Manual de AngularJS hemos explicado las bases del desarrollo basado en componentes y cómo funcionan los controladores en componentes. Pero todavia tenemos que aprender más cosas para lanzarnos a la arquitectura de aplicación basada en componentes.
En esta ocasión vamos a explicar cómo pueden unos componentes interoperar con otros, por medio del paso de datos o información para su funcionamiento. Para ello usamos mecanismos conocidos como el bindeo. Para quien ya esté familiarizado con el desarrollo de directivas seguramente le resultará todo muy familiar, aunque el que venga sin ese conocimiento previo tampoco ha de preocuparse porque es todo bastante sencillo.
Arquitectura de componentes
Cada componente es responsable por hacer una parte del trabajo de la aplicación, pero para trabajar no están solos, sino que deben relacionarse y comunicarse unos con otros.
Bajo la arquitectura de componentes tenemos un árbol de elementos. Forman una estructura donde unos componentes incluyen a otros, donde cada uno de ellos encapsula un HTML y cierta lógica de aplicación.
La clave es que cada componente tiene su scope (ámbito) independiente y único. Un componente solo conoce aquellos datos que su controlador maneja, no pudiendo acceder a los scope de otros componentes, ya sean padres o hijos. Esa estructura facilita el desarrollo de aplicaciones, porque uno puede saber a priori dónde está el código que afecta a cada parte de la aplicación.
Esto se ve mejor en contraposición con una aplicación tradicional de Angular:
- En una aplicación de Angular, desde cualquier componente del código podemos afectar a cualquier parte de la aplicación, ya que el ámbito se puede compartir por mecanismos como la herencia de scopes y los observadores. Es una situación práctica que a veces deriva en problemas al entender las aplicaciones y al depurarlas.
- En una aplicación de componentes cada componente afecta al HTML que tiene dentro y puede modificar los datos que se están mostrando dentro de ese HTML. Eso hace fácil localizar el código que afecta a los datos de cualquier parte de la aplicación.
Para que componentes independientes puedan colaborar entre sí, de modo que consigamos entre todos una aplicación, es necesario que se intercambien datos. Esto se hace por medio de mecanismos de binding y se pueden producir a varios niveles.
Cómo se pasan los datos a los componentes
En la arquitectura de componentes los datos se pasan por medio de propiedades en el HTML y para que funcione se tiene que realizar la declaración de los datos compartidos en el JS.
Las propiedades del HTML funcionan como modificadores de los componentes, enviando datos que pueden afectar al componente de diversas maneras.
<mi-componente dato="valor"></mi-componente>
En el anterior HTML tenemos una propiedad llamada "dato" que dentro del componente equivale a "valor".
En el Javascript del componente, en su definición, se debe declarar qué propiedades se pueden recibir y qué tipos de binding se van a implementar sobre ellas. Para ello se debe declarar cada una de las propiedades en el objeto "bindings", indicando un símbolo que aclare qué tipo de bindeo se va a realizar.
angular.module("componenteBinding", [])
.component("componenteBinding", {
templateUrl: "./js/components/componente-binding/componente-binding.html",
controller: "componenteBindingController"
},
bindings: {
user: "=",
num: "@"
}
});
En el código anterior se está declarando un binding con dos atributos, "user" y "num".
Tipos de bindeo
El bindeo entre componentes puede ser de varios tipos. Podemos tener binding a dos direcciones, algo típico de Angular, donde los datos viajan desde un componente al otro y desde el segundo al primero. Pero ahora en Angular 1.5 también podemos crear binding en una sola dirección, así como también se puede realizar envío de datos en crudo, que una vez se entregan no quedan bindeados ni en una dirección ni en otra.
En la declaración bingings, cada tipo de bindeo se define por medio de un símbolo, los principales son:
"=": esto sirve para entregar una referencia a un objeto. Por tanto cualquier cosa que se esté entregando se comparte entre componentes.
"@": esto sirve para entregar un valor. No existe binding de ningún tipo.
(Símbolo "menor qué"): esto sirve para bindear en una única dirección, o 1 way binding. El padre le transfiere al hijo un valor, pero aunque lo modifique el hijo el nuevo valor no viaja al padre. Sin embargo, si el padre lo cambia, sí se cambia en el hijo.
"&": esta última alternativa permite enviar un puntero a una función.
Ejemplo de bindeo en componentes
Antes de terminar vamos a ver un ejemplo para ilustrar todo lo que hemos aprendido sobre componentes a lo largo de los anteriores artículos. Es un ejemplo sencillo pero práctico. Son dos componentes que se transfieren información para completar un objetivo común, que es crear un listado de usuarios.
Tendremos un componente padre que es el listado completo de usuarios y un componente hijo que es cada uno de los item del listado (el detalle de un usuario único). Por decirlo de otro modo, el componente padre producirá una repetición y cada uno de los item de esa repetición será implementado por un componente hijo.
Hemos llamado a nuestros componentes "user-list" y "user-detail". Cada componente a su vez tiene dos archivos de código, uno el .js, con el registro del componente en sí, y otro .html con el HTML local de ese componente.
Comenzamos viendo el código de mi listado de usuarios: user-list
El archivo user-list.js
angular.module("userList", [])
.component("userList", {
templateUrl: "./js/components/user-list/user-list.html",
controller: function($http){
var vm = this;
$http.get("http://jsonplaceholder.typicode.com/users")
.then(function(respuesta){
vm.usuarios = respuesta.data;
})
}
});
Como puedes ver, me traigo el listado de usuarios de un servicio REST. Ese listado de usuarios lo vuelco en la variable vm.usuarios, que es el único dato que este controlador ofrece a la vista.
Código de user-list.html
<user-detail ng-repeat="item in $ctrl.usuarios" usuario="item" numero="{{$index}}"></user-detail>
El código HTML es un único componente que tiene un ng-repeat, por lo que se produce la iteración por todos los usuarios. Cada usuario se muestra con el componente user-detail. A ese componente le estamos pasando dos bindings, cuyos valores se entregan por atributos del HTML.
Es importante reparar la diferencia entre el atributo usuario="item" y numero="{{$index}}". La diferencia de las dos llaves es que {{$index}} lo vamos a pasar por valor, no por referencia. Lo confirmarás en el siguiente listado.
Código de user-detail.js
angular.module("userDetail", [])
.component("userDetail", {
templateUrl: "./js/components/user-detail/user-detail.html",
controller: function(){
var vm = this;
vm.cambiarEmail = function(){
vm.usuario.email = "miguel@desarrolloweb.com";
}
},
bindings: {
usuario: "=",
numero: "@"
}
})
Aquí la novedad está en "bindings", donde estamos declarando los dos bindeos que este componente espera recibir. Ahora lo importante es:
- El campo "usuario" lo recibe como bindeo. Es un objeto, pasado por referencia. En ese caso se produce un 2 way binding.
- El campo "numero" lo recibe como valor. Le llega solo un dato que, por mucho que se cambie en el componente, no se transfiere nada al padre.
Además fíjate que el controlador casi no tiene código. Solo hemos definido una función para cambiar un dato el objeto usuario, de modo que se compruebe si se produce el binding hacia arriba.
En la vista podremos usar los bindeos como si fueran datos proporcionados por el propio controlador y en el controlador también los podremos usar como si fueran propiedades suyas normales. Esto lo compruebas con el siguiente listado.
Archivo user-detail.html
<div class="user" ng-click="$ctrl.cambiarEmail()">
<h2>{{$ctrl.numero}})</h2>
Nombre: {{$ctrl.usuario.name}}
<br>
Email: {{$ctrl.usuario.email}}
</div>
Aprecia como los datos que me vienen por el binding los trato como si fueran del controlador: $ctrl.numero y $ctrl.usuario
Puedes encontrar el código de estos ejemplos de componentes en Angular 1.5 en Github.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...