Binding a atributo en Angular

  • Por
Qué es el binding a atributo, por qué debemos conocerlo. En qué casos aplica el binding de atributo y ejemplos de uso.

En este artículo vamos a conocer una cosa nueva en el Manual de Angular. Está relacionada con el binding, que hemos tratado en numerosas ocasiones, aunque se trata de un nuevo caso de binding del que no habíamos hablado anteriormente: el binding a atributo.

En si, usar el binding a atributo es muy sencillo, pues resulta en una sintaxis muy elemental que tenemos que usar. Lo complicado puede ser ver el concepto y saber los momentos en los que se debe usar. Desde luego son pocos, pero si no conocemos este concepto nos encontraremos a veces con errores de Angular que nos puede costar solucionar.

Comenzaremos por explicar qué se conoce como atributo, frente a una propiedad, y luego explicaremos la sintaxis que usaremos cuando necesitemos bindear a un atributo.

Qué es un atributo en Angular

Primero cabe decir que en este caso vamos a definir "atributo" en el contexto de Angular, ya que en el contexto de HTML entendemos atributo como los modificadores que acompañan a etiquetas.

<p class="miclase">En este párrafo usamos el atributo "class"</p>

El HTML tiene cientos de atributos y debes saber perfectamente cómo funcionan, pero no nos referimos a ellos, sino a atributos en el contexto del desarrollo en Angular.

Incluso en Angular, a menudo usamos la palabra "atributo" como sinónimo de "propiedad". En muchos casos podría valer meter todo en el mismo saco, pero lo cierto es que no siempre es lo mismo y Angular no trata a ambos de la misma manera.

Básicamente, propiedad se refiere a una característica de un objeto del DOM, definida por una propiedad del objeto de ese elemento del DOM. En muchas ocasiones, cuando escribes HTML existe una correspondencia directa entre lo que sería un atributo de HTML y lo que sería una propiedad del DOM.

Por ejemplo el atributo "id" del HTML corresponde directamente con la propiedad "id" del objeto del DOM. Pero existen atributos que no tienen un mapeo directo en el DOM, como es el caso de "colspan". Existen atributos de HTML que mapean a propiedades del DOM que no se llaman ni funcionan igual, como "class" y "classList". En general esta es la clave de la cuestión.

Nota: Sin entrar en Angular, atributos en el HTML que tienen correspondencia con el DOM, como "id" o "value". Sin embargo hay una diferencia fundamental en cuanto al funcionamiento de un atributo y de una propiedad. En el HTML los atributos sirven para inicializar propiedades del DOM, sin embargo, durante la vida del elemento la propiedad puede cambiar (escribiendo otra cosa en el campo de texto cambia su propiedad "value") pero el atributo permanece exactamente igual. El atributo se utilizó para inicializar la propiedad del objeto, pero luego aunque ese objeto cambie, el atributo en el HTML sigue igual. Pruébalo tú mismo. Coloca un campo de texto con un value="xx". Luego edita el campo de texto, colocando cualquier otra cadena. En el momento que lo editas cambia la propiedad "value" del DOM. Pero luego intenta hacer sobre el campo de texto "elementoInputDelDom.getAttribute('value')" y verás que el valor del atributo que te devuelve continua siendo "xx".

Pues bien, cuando hacemos bindeo a propiedades con Angular (property binding), tenemos disponibles las propiedades del elemento, pero no los atributos. Por tanto, podemos hacer esto:

<div [id]="idDivision">Test de binding a propiedad</div>

Pero no podemos hacer esto otro:

<td [colspan]="1 + 1">test</td>

En el momento en el que intentamos hacer un binding a propiedad, sobre una propiedad que no existe en el DOM del elemento y que Angular no entiende, obtendremos un mensaje como este: Can't bind to [...] since it isn't a known property of [...].

Ni tan siquiera podemos acudir a la interpolación de strings para resolver este problema. Si intentamos algo como esto tampoco funcionará:

<td colspan="{{1 + 1}}">test</td>

El asunto es que, tanto string interpolation como property binding son capaces de bindear únicamente a propiedades definidas en Angular y no a atributos del HTML.

Sintaxis para bindear a un atributo

La sintaxis necesaria para que Angular procese el binding a un atributo, sin producir ningún mensaje de error, es muy parecida a la sintaxis de binding a una propiedad que ya conocemos. Solo tenemos que especificar que en ese preciso momento estamos bindeando a un atributo. Si antes colocábamos la propiedad entre corchetes, ahora colocaremos "attr." seguido del nombre del atributo.

<td [attr.colspan]="1 + 1">test</td>

Por ver otro ejemplo, los "data attribute" tampoco tienen mapeo a propiedades. En estos casos, si queremos bindear a un "data attribute" del HTML5, tendremos que hacerlo con la sintaxis que acabamos de conocer.

Por si no sabes qué es un "data attribute", tiene un aspecto como este:

<div data-cliente="DesarrolloWeb.com">Desarrollo Web</div>

Ahora, si en el TypeScript de nuestro componente tenemos un objeto cliente definido, como podría ser:

cliente = {
  nombre: 'DesarrolloWeb.com'
}

Y queremos bindear ese nombre en el data attribute, haríamos algo como esto

<div [attr.data-cliente]="cliente.nombre">{{cliente.nombre}}</div>

Ejemplo completo de componente que implementa bindeo a atributo

Vamos a ver ahora un ejemplo de un componente que realiza el binding a atributo, de modo que podamos practicar algo. Es un componente que nos muestra los puntos donde se situa un polígono. Queremos mostrar los datos del polígono haciendo uso de una tabla en la que hay una casilla que está expandida en varias columnas.

La tabla resultante que queremos conseguir tendrá este aspecto:

PONER IMAGEN

Comenzaremos viendo la definición TypeScript de nuestro componente, en la que podremos observar la especificación de los datos de nuestro polígono en una propiedad "poligono" del componente.

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

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

  poligono = {
    nombre: 'cuadrado',
    puntos: [
      {
        x: 10,
        y: 5
      },
      {
        x: 15,
        y: 5
      },
      {
        x: 15,
        y: 10
      },
      {
        x: 10,
        y: 10
      }
    ]
  }
  constructor() { }

  ngOnInit() {
  }

}

Ahora veamos la parte más representativa de este componente, en el objetivo del artículo, de aprender el bindeo a atributos.

<table>
  <tr>
    <th>Nombre</th>
    <th>Lados</th>
    <th [attr.colspan]="poligono.puntos.length">Puntos</th>
  </tr>
  <tr>
    <td>{{poligono.nombre}}</td>
    <td>{{poligono.puntos.length}}</td>
    <td *ngFor="let punto of poligono.puntos">{{punto.x}},{{punto.y}}</td>
  </tr>
</table>

El bindeo de atributo nos permite hacer que la celda de la tabla donde pone "puntos" se expanda en un número de celdas igual al número de puntos del polígono. Dependiendo de la definición de ese polígono las celdas de la tabla se adaptarán a las necesidades de cada caso.

Luego nos quedaría simplemente colocar unos estilos, a placer. Que, para que se parezcan a la tabla que hemos mostrado en la imagen de antes, podrían ser como estos:

table {
  border: 1px solid darkgoldenrod;
  margin: 20px;
}
th {
  background-color: rgb(220, 224, 152);
  text-align: center;
  padding: 10px;
}
td {
  padding: 5px;
  border: 1px solid #ddd;
}

Eso es todo. Como has visto, el bindeo a atributos es muy sencillo. Sólo hay que tener claro el concepto y cuándo se debe aplicar. Ahora si te encuentras en una situación similar, simplemente acuérdate de este artículo, o busca en Google ;).