Material‎ > ‎Software‎ > ‎Angular 1.x con ES6‎ > ‎

Componentes propios

Primer ejemplo: mostrar un usuario

En una aplicación tenemos usuarios, y queremos mostrar los usuarios de manera uniforme en todas las vistas. Por ejemplo, nos piden que
  • el nombre del usuario se visualice en rojo o azul según su género (femenino o masculino)
  • también deberíamos mostrar una reina de ajedrez o un rey, dependiendo también del género
  • y por último deberíamos mostrar la frase de cabecera del usuario, debajo del nombre
Nuestro objetivo es
  • poder establecer en un solo lugar cómo es la visualización de un usuario
  • esto no solo implica la parte visual, sino que también hay comportamiento asociado a la tecnología de la vista (tal o cual color, este o aquel ícono)
La forma de encapsular un componente en Angular (a partir de la versión 1.5) es... justamente un componente definido por nosotros.

Modelo del componente

El usuario es una clase que no está atado a cuestiones tecnológicas: agrupa información y a lo sumo puede decirnos si el usuario es una mujer mediante un método calculado.

const FEMENINO = "F"
const MASCULINO = "M"

class Usuario {

constructor(nombre, fraseCabecera, sexo) {
this.nombre = nombre
this.fraseCabecera = fraseCabecera
this.sexo = sexo
}

esMujer() {
return this.sexo === FEMENINO
}
}
/js/domain/usuario.js

Controller y definición del componente

El componente es una clase a la que podemos configurarle
  • bindings contra parámetros que vamos a pasarle
  • un controller que sí está atado a la tecnología (ya que el componente lo define Angular)
  • y una vista html
const usuarioViewer = {
bindings: {
usuario: '<'
},
controller: class UsuarioViewerController {
constructor() { }
get usuarioClass() {
if (this.usuario.esMujer()) {
return "danger"
} else {
return "info"
}
}
},
templateUrl: './partials/usuario.html'
}
/js/components/usuarioViewer.js

En el caso de nuestro componente usuarioViewer, recibiremos un usuario donde el binding será unidireccional (por eso el símbolo <) y el controller permite definir la class danger o info para el label del nombre.

Vista del componente

La vista html contiene los tags necesarios de bootstrap-twitter para darle un formato más rico:

<div class="panel panel-default">
<div class="panel-body panel-heading">
<div class="col-md-1">
<div ng-if="$ctrl.usuario.esMujer()">
<h3 class="glyphicon glyphicon-queen" aria-hidden="true" title="Mujer"></h3>
</div>
<div ng-if="!$ctrl.usuario.esMujer()">
<h3 class="glyphicon glyphicon-king" aria-hidden="true" title="Hombre"></h3>
</div>
</div>
<div class="col-md-11">
<h3>
<span class="label label-{{$ctrl.usuarioClass}}">
{{$ctrl.usuario.nombre}}
</span>
</h3>
<div>{{$ctrl.usuario.fraseCabecera}}</div>
</div>
</div>
</div>
/partials/usuario.html

Aquí vemos el uso de otro componente: ng-if para mostrar el ícono de reina/rey para el género femenino/masculino respectivamente.

Últimos ajustes

Tenemos un controller que simplemente define una lista de usuarios:

class UsuarioController {
constructor() {
this.usuarios = [
new Usuario("Gabriel Graves", "Soy el Brad Pitt de Lugano", MASCULINO),
new Usuario("Javier Zolotarchuk", "Tengo el corazón mirando al Sur...", MASCULINO),
new Usuario("Clara Allende", "Git Git Scala Git", FEMENINO)
]
}

}
/js/controller/usuario.js

En el archivo app.js asociamos el controller y el componente con nuestra aplicación Angular:

const componentesApp = angular.module('componentesApp', [ 'ui.bootstrap' ])

const usuarioController = componentesApp.controller('UsuarioController', UsuarioController)
componentesApp.component('usuarioViewer', usuarioViewer)

app.js

Y por último, en nuestro index.html vamos a recorrer con el componente ng-repeat cada uno de los usuarios, pero delegando al componente usuariosViewer la forma de mostrarlo en html. 

<div ng-controller="UsuarioController as usuarioCtrl">
                <div ng-repeat="usuario in usuarioCtrl.usuarios">
                    <usuario-viewer usuario="usuario"/>
                </div>
            </div>
index.html

Entonces el usuario que le paso al componente mediante la definición
<usuario-viewer usuario="usuario"/>
se traduce en el componente como un binding
const usuarioViewer = {
bindings: {
usuario: '<'
}

Y se usa en el html como
{{$ctrl.usuario.nombre}}




Convención de Nombres
Nótese que si bien definimos la directiva como "usuariosViewer", luego desde el html se utilizó como "usuarios-viewer". Eso es porque Angular automáticamente traduce el nombre que esté en camelCase a la notación de palabras (en minúscula) separadas por guión medio. Este detalle es importante para no quedarse trabado al definir un componente.

Ejemplos:
 'errorPanel'... ...<error-panel>
 'menuItemImportante' ... ...<menu-item-importante>

Segundo ejemplo: ingreso de un número entero

Si queremos ingresar un número entero, podemos definir un input type="number" y HTML5 trae
  • validaciones incorporadas para evitar que carguen un valor alfabético.
  • un spinner para subir o bajar valores
Ahora bien, si nosotros queremos darle un look & feel más parecido a bootstrap twitter pero sin tener que repetir esa lógica una y otra vez, un componente es nuevamente una buena opción.

Modelo del componente

Nuevamente tenemos un modelo asociado al componente, un contador al que puedo sumar o restar un valor. No hay elementos que se relacionen con la tecnología de la vista.

class Contador {
constructor() {
this.valor = 0
}

sumar() {
this.valor++
}

restar() {
this.valor--
}
}
/js/domain/contador.js

Controller y definición del componente

Dado que queremos mostrar de diferentes colores los números positivos o negativos, el componente debe definir un controller propio para mostrar los colores que definen las clases de bootstrap twitter. También debe tener un binding bidreccional con el contador, ya que la vista puede sumar o restar el valor del contador, esto se delimita con el caracter '=' en lugar del '<':

const contadorSpinner = {
bindings: {
contador: '='
},
controller: class ContadorSpinnerController {
constructor() {}
get valorClass() {
if (this.contador.valor > 0) {
return "primary"
} else {
return "warning"
}
}
},
templateUrl: './partials/contador.html'
}
/js/components/contadorSpinner.js

Vista del componente

El componente tiene un label para mostrar el valor, de diferentes colores (lo resuelve como vimos recién el controller del componente), y dos botones cuyo binding se establece contra las operaciones sumar() y restar() definidas para el contador, nuestro objeto de dominio.

<h1>
<button type="button" class="btn btn-info btn-lg" ng-click="$ctrl.contador.restar()">
<span class="glyphicon glyphicon-minus" aria-hidden="true"></span>
</button>
<span class="label label-{{$ctrl.valorClass}}">{{$ctrl.contador.valor}}</span>
<button type="button" class="btn btn-info btn-lg" ng-click="$ctrl.contador.sumar()">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
</button>
</h1>
/partials/contador.html

Últimos ajustes

Nuevamente agregamos un controller que simplemente crea un contador como modelo:

class ContadorController {
constructor() {
this.contador = new Contador()
}
}
/js/controller/contador.js

En el app.js incorporamos el nuevo controller (¡cuánta burocracia! ¿no?):

const componentesApp = angular.module('componentesApp', [ 'ui.bootstrap' ])

const usuarioController = componentesApp.controller('UsuarioController', UsuarioController)
const contadorController = componentesApp.controller('ContadorController', ContadorController)
componentesApp.component('usuarioViewer', usuarioViewer)
componentesApp.component('contadorSpinner', contadorSpinner)

app.js

Y por último en el index.html podemos definir dos contadores, cada uno mantiene su propio estado (se crean dos objetos contador):

                <div class="panel panel-default">
                    <div ng-controller="ContadorController as contadorCtrl">
                        <contador-spinner contador="contadorCtrl.contador"/>
                    </div>
                    <div ng-controller="ContadorController as contadorCtrl">
                        <contador-spinner contador="contadorCtrl.contador"/>
                    </div>
                </div>
De esa manera tenemos nuestros contadores...

Referencias / Tutoriales


Comments