> Manuales > Manual de Lit

Explicamos los conceptos básicos de interoperabilidad y cómo unos componentes comunican con otros, usando el patrón mediador. Veremos ejemplos de data binding entre componentes para comunicación de padres a hijos.

Interoperabilidad entre componentes Lit

La gracia del desarrollo de componentes es que unos componentes se puedan basar en otros para cumplir sus objetivos, creando componentes pequeños en los que se apoyan otros componentes más complejos.

En el desarrollo frontend podemos llevar la composición de componentes hasta un nivel tal que es posible construir toda una aplicación completa dentro de un componente. Por supuesto, ese componente se apoyará en un árbol de componentes con responsabilidades muy definidas. No vamos a llegar tan rápido a esa situación, pero sí es un objetivo que nos planteamos para un futuro cercano y desde este momento queremos aclarar que es perfectamente viable.

No es necesario usar ningún framework como Angular, Vue, NextJS o librerías como React para construir aplicaciones frontend. Podemos perfectamente usar Lit para construir cualquier sistema tan complejo como sea necesario. Gracias a la composición de componentes es perfectamente viable y con un poco de conocimiento y organización podemos obtener unos resultados tan buenos como los que tendríamos al usar un framework.

En este artículo vamos a comenzar a entender cómo unos componentes colaboran con otros para conseguir la interoperabilidad necesaria para llevar a cabo componentes complejos que se apoyen en otros componentes más sencillos.

Patrón mediador

Vamos a aclarar primero cuál es el modelo o patrón de comunicación entre componentes. Lo llamamos "patrón mediador" (en inglés mediator) y básicamente define cómo se realizará el flujo de datos para la colaboración entre componentes.

Podemos encontrar comunicación de datos entre componentes en dos direcciones:

Este patrón ya lo hemos explicado en otras ocasiones en manuales anteriores de librerías basadas en Web Components, por lo que te invitamos a leer este artículo del Manual de LitElement que todavía sigue funcionando exactamente igual en Lit: Patrón mediador ¿Dónde está el doble binding en LitElement?

En la siguiente imagen tienes un diagrama de este patrón. Las cajas son componentes. Como hemos dicho, unos componentes se pueden apoyar en otros para resolver problemas mayores, creando un árbol de componentes.

Esquema de comunicación entre componentes Lit mediante el patrón mediador

Las comunicaciones de arriba hacia abajo (padres a hijos) se realizan mediante propiedades. El componente padre le pasa las propiedades que el hijo deba conocer. Si un componente nieto necesita conocer una propiedad del padre, entonces el padre se la pasa al hijo y el hijo se la debe pasar al nieto.

Las comunicaciones de abajo a arriba se realizan disparando eventos, generalmente eventos personalizados que el propio componente puede generar. El componente hijo dispara un evento. El componente padre puede definir un manejador de evento que capture cualquier evento que necesite escuchar del hijo. En el evento se puede además enviar cualquier conjunto de datos que se necesite comunicar a quienes escuchan el evento.

Si un nieto necesita comunicar con su abuelo, entonces debe configurar el evento para que suba por la burbuja de eventos (que es algo nativo de Javascript) con la propiedad bubbles del objeto evento y generalmente también con la propiedad composed para que traspase el shadow DOM.

Cualquier evento configurado para que suba por la burbuja permite que sea escuchado por cualquier componente o elemento del DOM que haya hacia arriba, llegando finalmente al objeto document o en último caso a window.

El trabajo con eventos en general y eventos personalizados en particular en componentes Lit se realiza siempre de manera nativa, es decir, con el propio API del DOM disponible en Javascript, por lo que no tiene nada en particular aportado por la librería Lit.

Puedes encontrar un artículo donde se explica cómo trabajar con eventos personalizados, que te aclarará los puntos sobre Javascript que debes conocer para poder manejarlos.

Data binding en Lit

El "data binding", o en español el "enlace de datos" es la utilidad que ofrecen los frameworks para que, de manera declarativa, podamos definir cómo se propagarán los cambios en las propiedades.

Gracias al data binding, cuando una propiedad cambia, se actualiza el template del componente. Eso ya lo hemos visto en varias ocasiones en este manual. Pero además, lo que vamos a aprender ahora es que los cambios de una propiedad se pueden propagar también a los componentes hijos, provocando consecuentemente actualizaciones en sus respectivos templates.

El data binding no ha variado desde LitElement a Lit, por lo que todo lo que sabes de LitElement lo puedes aplicar directamente a Lit. Por ese motivo, no vamos a repetirnos de nuevo y os pido realizar una lectura del artículo de binding de datos en LitElement, ya que todo lo que se explica lo puedes aplicar también aquí.

Ejemplo de data binding de padres a hijos

Vamos a ver un ejemplo de funcionamiento del enlace de datos para el envío de valores de propiedades de padres a hijos. Solamente vamos a tratar de momento esta dirección del flujo de datos, de las dos que hemos señalado que pueden ocurrir en la interoperabilidad de componentes.

Comenzamos con algo sencillo, como es el envío de propiedades de tipo String, que son las más sencillas. Más adelante veremos ejemplos de componentes que pasan datos de otros tipos.

Para este ejemplo vamos a recuperar el componente de lista de etiquetas "dw-tag-list", que desarrollamos en un pasado artículo, dedicado a los bucles en los templates.

La novedad que vamos a incorporar es un segundo componente llamado "dw-tag" que implementará una etiqueta individual. Este componente se encargará solamente de mostrar una etiqueta con un estilo particular, que quedará encapsulado en el componente. Además, más adelante estas etiquetas sueltas podrán tener otras funcionalidades si fuera necesario.

El componente dw-tag tendrá un código como este:

import { LitElement, html, css } from 'lit';

export class DwTag extends LitElement {
    static styles = [
        css`
            :host {
                display: inline;
            }
            span {
                display: inline-block;
                padding: 0.2rem 0.4rem;
                border-radius: 0.2rem;
                background-color: #666;
                color: #fff;
                font-weight: bold;
            }
        `
    ];

    static properties = {
        name: { type: String },
    }

    render() {
        return html`<span>${this.name}</span>`;
    }
}
customElements.define('dw-tag', DwTag);

Ahora vamos a usar este componente dentro de dw-tag-list, de modo que un componente se apoyará en otro para resolver problemas más grandes y vamos introduciéndonos en la mencionada interoperabilidad de componentes.

El componente dw-tag-list lo desarrollamos anteriormente, en el artículo dedicado a Repeticiones o bucles, en templates lit

Básicamente la novedad ahora será que en el template de dw-tag-list tenemos que usar el componente dw-tag para mostrar cada una de las etiquetas. El template actualizado quedará así:

render() {
    return html`
        ${this.tags.length == 0
            ? html`<p>No tenemos tags que mostrar</p>`
            : html`
                <ul>
                    ${this.tags.map( tag => html`<li><dw-tag name="${tag}"></dw-tag></li>`)}
                </ul>
            `
        }
    `;
}

Como puedes ver, el ejercicio es tan sencillo como cambiar el marcado, colocando la nueva etiqueta <dw-tag>. A esta etiqueta se le pasa en su propiedad "name" el nombre del tag actual. Con esto ya tenemos declarado el paso de valores del componente padre al hijo. Lit se encarga de todo el trabajo gracias al data binding. Por supuesto, si el array de tags cambia, se avisará a los componentes hijos con los nuevos valores de "name" de los tag.

Importar el código del componente

También es muy importante importar el código de los componentes. Todo componente padre debería hacer los "import" de todos los componentes hijos que necesite usar. De esta manera, cuando se use el padre siempre conoceremos a los componentes hijos que se necesitan para trabajar.

En este caso el import, como son dos archivos que están en la misma carpeta, se hace así:

import './dw-tag';

Para ver el código completo de los componentes que hemos utilizado os dejo el enlace al repositorio de GitHub, en el commit donde hemos incorporado los cambios explicados en este artículo.

Conclusión

Hemos comenzado a ver la interoperabilidad entre componentes, lo que resulta esencial para las aplicaciones y para el desarrollo de custom elements complejos. Una buena componetización (nos referimos a crear una buena estructura de componentes que se repartan las responsabilidades) resultará básica para poder conseguir código fácilmente mantenible.

En vez de crear componentes muy complejos que se encargan de muchas cosas a la vez, que son difíciles de mantener por su propia complejidad, debemos crear componentes sencillos, que se apoyan en otros componentes para resolver los problemas. Esto nos ayudará mucho no solo a dar mantenimiento más sencillo a las aplicaciones, sino también a reutilizar los componentes y eliminar duplicidades de código.

En futuros artículos veremos ejemplos de componentes que envían datos de otros tipos, como boleanos u objetos, que tienen sintaxis distintas para su data binding. Además, dentro de lo que se refiere al patrón mediador nos quedará pendiente la gestión de eventos para comunicar de hijos a padres, que es todo mediante Javascript nativo. No os preocupéis porque también habrá tiempo para ver ejemplos representativos más adelante.

Miguel Angel Alvarez

Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...

Manual