Tuesday, December 20, 2022

LWC Handle Events

Handle Events

There are two ways to listen for an event: declaratively from the component’s HTML template, or programmatically using an imperative JavaScript API. It’s better to listen from the HTML template since it reduces the amount of code you have to write. To handle events, define methods in the component’s JavaScript class.

Attach an Event Listener Declaratively

Declare the listener in markup in the template of the owner component, in this example, c-parent.

<!-- parent.html -->
<template>
    <c-child onnotification={handleNotification}></c-child>
</template>

Define the handler function, in this example handleNotification, in the c-parent JavaScript file.

// parent.js
import { LightningElement } from 'lwc';
export default class Parent extends LightningElement {
    handleNotification(){
        // Code runs when event is received
    }
}

Attach an Event Listener Programmatically

Define both the listener and the handler function in the c-parent JavaScript.

// parent.js
import { LightningElement } from 'lwc';
export default class Parent extends LightningElement {
  constructor() {
    super();
    this.template.addEventListener('notification', this.handleNotification);
  }
  handleNotification = () => {};
}

If the same listener is added to the same element repeatedly, the browser ignores the duplicates.

If the event listener isn’t removed, you can choose to inline the handleNotification code in the addEventListener() call.

this.template.addEventListener('notification', evt => {
  console.log('Notification event', evt);
});

Important Don’t use addEventListener(eventName, this.handleNotification.bind(this)). It’s an anti-pattern because bind() returns a new function and thus the component can’t call removeEventListener() with the same function instance. Because the component can’t use the same function instance, the listener creates a memory leak.

There are two syntaxes for adding an event listener. One to add an event listener to an element within a component’s shadow boundary, and one to add an event listener to an element that the template doesn’t own, for example, an element passed into a slot.

To add an event listener to an element within the shadow boundary, use template.

this.template.addEventListener()

To add an event listener to an element that a template doesn’t own, call addEventListener directly.

this.addEventListener()

Get a Reference to the Component That Dispatched the Event

To get a reference to the object that dispatched the event, use the Event.target property, which is part of the DOM API for events.

Example Several recipes in the lwc-recipes repo use Event.target. To find examples, search the repo for Event.target.

Event Retargeting

When an event bubbles up the DOM, if it crosses the shadow boundary, the value of Event.target changes to match the scope of the listener. This change is called “event retargeting.” The event is retargeted so the listener can’t see into the shadow DOM of the component that dispatched the event. Event retargeting preserves shadow DOM encapsulation.

Let’s look at a simple example.

<!-- myButton.html -->
<template>
    <button>{label}</button>
</template>

A click listener on <my-button> always receives my-button as the target, even if the click happened on the button element.

Imagine an event is dispatched from a div element in the c-todo-item component. Within the component’s shadow DOM, Event.target is div. But to a listener on the p element in the containing c-todo-app component, the Event.target is c-todo-item, because the p element can’t see into the c-todo-item shadow DOM.

<c-todo-app>
  #shadow-root
    <div>
        <p>Your To Do List</p>
    </div>
    <c-todo-item>
      #shadow-root
        <div>
            <p>Go to the store</p>
        </div>
    </c-todo-item>
</c-todo-app>

It’s interesting to note that to a listener on c-todo-item, the Event.target is c-todo-item, not div, because c-todo-item is outside the shadow boundary.

Listen for Changes to Input Fields

To listen for changes from an element in your template that accepts input, such as a text field (<input> or <lightning-input>), use the onchange event.

<!-- form.html -->
<template>
    <input type="text" value={myValue} onchange={handleChange} />
</template>
// form.js
import { LightningElement } from 'lwc';
export default class Form extends LightningElement {
    myValue = 'initial value';

    handleChange(evt) {
        console.log('Current value of the input: ' + evt.target.value);
    }
}

In this example, the handleChange() method in the JavaScript file is invoked every time the value of the input changes.

The myValue property represents the value for the input element. This property value is not updated automatically on every change.

You might want extra validation of the value the user enters for auto-correction or restriction of some values as they type. To keep myValue synchronized with the current value of the input, update myValue in the handleChange() method. The following auto-corrects the typed value by removing white spaces at the beginning and end of the string. Use evt.target.value to get the current value of the input field.

// form.js
import { LightningElement } from 'lwc';
export default class Form extends LightningElement {
    myValue = 'initial value';

    handleChange(evt) {
        const typedValue = evt.target.value;
        const trimmedValue = typedValue.trim(); // trims the value entered by the user
        if (typedValue !== trimmedValue) {
            evt.target.value = trimmedValue;
        }
        this.myValue = trimmedValue; // updates the internal state
    }
}

This example shows how to reset the input value property to the trimmed value in line evt.target.value = trimmedValue. It also shows how to keep myValue property in sync with the normalized value in case the component gets rehydrated (loaded with new values) in the future.


Note Mutating properties from elements defined through a template could have undesired side effects elsewhere in the component. In our example, the input value property of the element changes from the value defined in the template represented by evt.target. The example changes the value used in the template (myValue) to keep the state of the component elements in sync. Otherwise, template rehydration detects a mismatch of the property value while attempting to reconcile the state of the input element with the state of your component. This mismatch generates a runtime warning, and you need to adapt your component or JavaScript to keep the integrity of the data throughout your template.

Remove Event Listeners

The framework takes care of managing and cleaning up listeners for you as part of the component lifecycle. However, if you add a listener to anything else (like the window object, the document object, and so on), you’re responsible for removing the listener yourself.

To remove the event listeners, use the disconnectedCallback lifecycle hook.

No comments:

Post a Comment