> Manuales > Manual de Lit

Cómo asignar manejadores de eventos desde los métodos del ciclo de vida connectedCallback y disconnectedCallback del estándar de Web Components, que también nos sirven de utilidad en el desarrollo de componentes Lit.

Ejemplo con connectedCallback y disconnectedCallback en un componente Lit

En el artículo anterior comenzamos a analizar métodos del ciclo de vida de los componentes de Lit. Vimos que existen métodos nativos que vienen del estándar de Web Componentes, así como métodos del ciclo de vida incluidos por la propia librería Lit. En este artículo vamos a seguir abordando métodos nativos, haciendo otros ejemplos relevantes de uso de connectedCallback y disconnectedCallback.

Para ilustrar este uso de modo un poco más avanzado, vamos a hacer un componente que está pendiente del scroll que se realiza en el documento para que, si el usuario no está en la parte de arriba de la página, muestre una interfaz para que se pueda hacer clic sobre ella y desplazarse arriba del todo del documento.

¿Sabéis a qué tipo de componente me refiero? Es una interfaz bastante usada en el mundo de la web, sobre todo útil cuando las páginas son largas y hay mucho scroll para volver a la parte de arriba, donde suelen estar los enlaces a otras secciones del sitio web.

Mi objetivo no es tanto el componente en sí sino aprender a crear y eliminar los manejadores en los momentos del ciclo de vida adecuados, que en este caso son connectedCallback y disconnectedCallback. De todos modos, el componente será perfectamente funcional. Tú le podrías dar otro estilo más profesional, hacer animaciones de entrada y salida de la interfaz con CSS y cosas similares.

Propiedad para manejar el estado del componente

Vamos a comenzar por las partes simples del componente que no requieren muchas explicaciones. Empezaremos realizando la declaración de una propiedad "visible" que permite definir la visibilidad del enlace que aparecerá para ir a la parte superior de la página.

static get properties() {
    return {
        visible: { 
            type: Boolean,
            reflect: true,
            },
    };
}

Esa propiedad sirve para activar un estilo CSS que aplicaremos mediante un selector de atributo, de modo que cuando el componente esté visible se muestre la interfaz. Esto lo hacemos con una técnica que ya vimos en el artículo Propiedades reflect en Lit.

El template del componente es este:

render() {
    return html`
        <div @click=${this.scrollTop}><svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg></div>
    `;
}

Simplemente es una imagen svg que usamos como icono para subir arriba, que está metida dentro de una etiqueta <div>.

Además tenemos declarado un manejador de evento click que invoca al método scrollTop de este componente, que tendrá este código:

scrollTop() {
    window.scroll( {top: 0, left: 0, behavior: 'smooth' });
}

Ese método de scroll lleva el documento hacia arriba y lo hace además con una animación suavizada, por lo que resultará bastante atractivo de cara a la experiencia de usuario.

Eventos de scroll en el objeto window

Hasta aquí la verdad es que no había mucha dificultad. Ahora lo que necesitamos requiere otros conocimientos, básicamente para la parte de asociar los manejadores al objeto window, de modo que controlemos el scroll de la página, pues solamente queremos que se vea esa interfaz cuando se ha desplazado el documento para abajo.

Hacer este ejercicio resultaría fácil, pues es solo crear un manejador de evento, pero hacerlo bien no será tan sencillo. Básicamente la necesidad importante es que podamos poner y quitar los manejadores en los métodos del ciclo de vida adecuados, de manera que si el componente se coloca y se quita del DOM varias veces, los eventos de escuchar el scroll se quiten y se pongan también.

Para ello vamos a usar los métodos connectedCallback y disconnectedCallback de manera similar a como lo hicimos en el anterior artículo del manual.

connectedCallback() {
    super.connectedCallback();
    document.addEventListener('scroll', this.changeVisibilityHandler);
}
disconnectedCallback() {
    super.disconnectedCallback();
    document.removeEventListener('scroll', this.changeVisibilityHandler);
}

Resulta bastante fácil, ¿no? Podría ser que sí, pero para hacer esto hay un matiz importante. Tal como lo hemos definido, el método this.changeVisibilityHandler podría parecer un método normal del componente, el problema aquí es que dentro de ese método no podríamos acceder a this con el valor del componente en sí, sino que sería el valor del elemento sobre el que se está escuchando el manejador. Esto es un comportamiento de Javascript en general que no sé si conoces.

Método bind de las funciones

¿Conoces el método `bind()`` que podemos invocar sobre las funciones? Lo hemos usado en multitud de artículos de DesarrolloWeb y es básico en el desarrollo frontend para solucionar algunos comportamientos muy específicos de Javascript.

En resumen, para quienes no lo conozcan, podemos decir que `bind()`` sirve para cambiar el scope de this en la ejecución de una función, pudiendo enviar cualquier valor que queramos que tome la variable this al ejecutarse.

Un caso de uso muy habitual es cuando estamos definiendo un manejador de eventos con addEventListener:

En este ejemplo concreto:

document.addEventListener('scroll', this.changeVisibilityHandler);

Lo que nos ocurrirá es que no podemos acceder a this, es decir, al componente donde estamos trabajando, dentro del método changeVisibilityHandler. Pero para nosotros es esencial poder acceder al componente porque dentro de ese método querremos cambiar a veces la propiedad "visible".

Para ello podemos bindear this usando el método bind de las funciones, de esta manera:

document.addEventListener('scroll', this.changeVisibilityHandler.bind(this));

Quitar el evento con removeEventListener

Al desconectarse el componente del DOM necesitamos quitar el evento. Para ello usamos el método `removeEventListener()``, enviando el tipo de evento que queremos afectar y la función manejadora que se desea quitar. Sin embargo, ahora nos encontramos ante otro problema y es que no tenemos clara la función manejadora.

la expresión this.changeVisibilityHandler.bind(this) lo que produce es una nueva función, que es la que tenemos que enviar a removeEventListener. Pero esa función no la hemos guardado en ninguna parte, por lo que vamos a tener que complicarlo un poquito más.

Simplemente en el constructor vamos a almacenar en un atributo del objeto la función manejadora, una vez bindeado el this:

constructor() {
    super();
    this.visible = false;
    this.changeVisibilityHandler = this.changeVisibility.bind(this);
    this.changeVisibility();
}

Luego usamos el esa referencia a la función al asociar los manejadores en connectedCallback y disconnectedCallback.

Código completo del componente "ir al top"

Creo que con esto ya estamos en condiciones de ver el código completo de este componente.

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

export class DwScrollTop extends LitElement {
    static styles = [
        css`
            :host {
                display: block;
            }
            div {
                display: none;
                position: fixed;
                bottom: 1.5rem;
                right: 1.5rem;
                padding: 1rem;
                background-color: #ddd;
                border-radius: 0.3rem;
                
            }
            svg {
                width: 32px;
                height: 32px;
            }

            :host([visible]) div {
                display: block;
            }
        `
    ];

    static get properties() {
        return {
            visible: { 
                type: Boolean,
                reflect: true,
                },
        };
    }

    constructor() {
        super();
        this.visible = false;
        this.changeVisibilityHandler = this.changeVisibility.bind(this);
        this.changeVisibility();
    }
    connectedCallback() {
        super.connectedCallback();
        document.addEventListener('scroll', this.changeVisibilityHandler);
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        document.removeEventListener('scroll', this.changeVisibilityHandler);
    }

    render() {
        return html`
            <div @click=${this.scrollTop}><svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg></div>
        `;
    }

    changeVisibility() {
        console.log('Scroll es: ', document.documentElement.scrollTop);
        if(document.documentElement.scrollTop > 0) {
            this.visible = true;
        } else {
            this.visible = false;
        }
    }

    scrollTop() {
        window.scroll( {top: 0, left: 0, behavior: 'smooth' });
    }
}
customElements.define('dw-scroll-top', DwScrollTop);

Como puedes ver en el código, hemos ido explicando las distintas partes del componente. Solamente nos quedaba por comentar el método changeVisibility que básicamente comprueba si estamos en una posición de scroll diferente que el valor 0, que indicaría que estamos en el top de la página. Si es mayor que cero es que se ha hecho desplazamiento, por lo que la visibilidad del componente sería true. Si estamos en la posición 0, quiere decir que estamos en la parte de arriba del documento, por lo que la propiedad visible debería ser false.

Conclusión

Con este artículo hemos realizado un ejemplo práctico sencillo pero útil y muy relevante en relación al uso de los callbacks del ciclo de vida connectedCallback y disconnectedCallback. Recuerda que estos métodos son nativos de Web Components en general, por lo que podrías hacer uso de ellos en cualquier lugar donde se trabaje con Web Components y no solamente con la librería Lit. Además, hemos aprendido a realizar un comportamiento de scroll suavizado con Javascript. Quedaría de tu parte aplicarle algo más de gracia a la interfaz de scroll si lo deseas.

En el siguiente artículo vamos a seguir trabajando con métodos del ciclo de vida de los componentes, aunque ya con uno de los métodos que nos ofrece la propia librería Lit, como es firstUpdated().

Miguel Angel Alvarez

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

Manual