Start tracking your progress
Trailhead Home
Trailhead Home

Handle Events in Lightning Web Components

Learning Objectives

After completing this unit, you’ll be able to:
  • Create an app that includes multiple components.
  • Describe the file structure of a complex component.
  • Handle events.

Follow an Event’s Journey

You’ve built a component and pushed it to a scratch org. Let’s start adding some interactivity with event handling. We follow an event’s journey through several components for sophisticated event handling in an application. This application is a product selector for a bicycle shop. Users click a bike name and image to see more detail.

The bike selector component parts.

This app has four components working together.

  1. tile: displays an individual item.
  2. list: arranges the tiles.
  3. detail: displays item details when a tile is clicked (similar to the bikeCard you just created).
  4. selector: contains the whole set of components. A container component isn’t required, but we’re using one here to help with event handling.

For now, the application uses a data file to load static data for testing. In the next unit, you learn how to pull dynamic data from an org.

Component Composition

Let’s add some files to our project that we can deploy to an org.

  1. Download the files for this app here: Bike Selector App for Trailhead.
  2. Unzip the files into the lwc directory of the project for the E-bikes demo. Bike selector app file structure.

Component Relationships

In this app, multiple components work together; some components are nested inside other components. Just as you nest HTML elements inside each other, Lightning web components—which are just custom HTML elements—can be nested inside other Lightning web components.

In our file system, the components’ folders don’t really give insight into the relationships between them.

Let’s look at how the components nested at the UI level in a diagram. Bike selector app component parent/child relationships.

The selector component lays out the page and renders the list and detail component. The list component renders several tile components, one for each bike in the data.

<template>
    <div class="wrapper">
    <header class="header">Available Bikes</header>
    <section class="content">
        <div class="columns">
        <main class="main" >
            <c-list onproductselected={handleProductSelected}></c-list>
        </main>
        <aside class="sidebar-second">
            <c-detail product-id={selectedProductId}></c-detail>
        </aside>
        </div>
    </section>
    </div>
</template>

If you look in detail.html, you see conditional rendering. If nothing has been chosen from the list, then what’s displayed is a message asking the user to choose something. If something is chosen, it displays the bike information.

<template>
    <template if:true={product}>
        <div class="container">
            <div>{product.fields.Name.value}</div>
            <div class="price">{product.fields.MSRP__c.displayValue}</div>
            <div class="description">{product.fields.Description__c.value}</div>
            <img class="product-img" src={product.fields.Picture_URL__c.value}></img>
            <p>
                <lightning-badge label={product.fields.Material__c.value}></lightning-badge>
                <lightning-badge label={product.fields.Level__c.value}></lightning-badge>
            </p>
            <p>
                <lightning-badge label={product.fields.Category__c.value}></lightning-badge>
            </p>
        </div>
    </template>
    <template if:false={product}>
        <div>Select a bike</div>
    </template>
</template>

This nesting is achieved in the HTML for each parent component. For example, the list component has the following HTML, including the tile component as c-tile.

<template>
    <div class="container">
        <template for:each={bikes} for:item="bike">
            <c-tile 
                key={bike.fields.Id.value} 
                product={bike} 
                ontileclick={handleTileClick}>
            </c-tile>
        </template>
    </div>
</template>
Notice how each iteration of the bike item spawns a new tile component. Simply including the c-tile component tag makes each tile component its child. The div class definition “container” is used for styling, so you can control the arrangement of tiles. If you look at list.css, you see that it wraps the content.
.container {
   display: flex;
   flex-direction: row;
   flex-wrap: wrap;
}

The parent/child relationship is important, not only for the design of the app, but also for event handling.

Let’s dig a little deeper into the event handling.

Handling Events in HTML

So our app needs to handle one type of event—the user clicking a tile. When this happens, the detail component should re-render with the information from the related tile. You can handle events in HTML (add an event listener in the template) or JavaScript (write an event listener function). We recommend using the HTML approach, as follows.

Each tile component listens for the user click because the tile component’s HTML (tile.html) contains an onclick event listener.

<template>
    <div class="container">
        <a onclick={tileClick}>
            <div class="title">{product.fields.Name.value}</div>
            <img class="product-img" src={product.fields.Picture_URL__c.value}></img>
        </a>
    </div>
</template>
When a user clicks one of the tile instances in the UI, the onclick listener calls the handler function tileClick in the tile.js JavaScript file.
import { LightningElement, api } from 'lwc';

export default class Tile extends LightningElement {
    @api product;

    tileClick() {
        const event = new CustomEvent('tileclick', {
            // detail contains only primitives
            detail: this.product.fields.Id.value
        });
        // Fire the event from c-tile
        this.dispatchEvent(event);
    }
}

Events Up, Properties Down

In a complex component (one that contains several parent and child components) we recommend you propagate the event up through the component hierarchy, so parent components can respond to child events. If you have other child components (not the one firing the event) you can pass a property down to those children in response to the event.

The pattern looks like this:

Event flow through the component hierarchy.

To do this, we need to chain event listeners and handlers up the hierarchy to the ebikes component. Then pass a property down to the detail component.

In our example files you see the following.

  1. tile.html has the onclick event listener that calls the tileClick handler.
  2. tile.js has the tileClick method that creates a new CustomEvent object containing a detail value (this.product.fields.Id.value).
  3. List.html has the ontileClick listener that calls the handleTileClick handler.
  4. List.js has the handleTileClick method that creates another event object (evt) also containing a detail value. And it dispatches the event in JavaScript:
    // Fire the event from c-list 
    this.dispatchEvent(event);
  5. selector.html has the onproductselected event listener that calls the handleProductSelected handler.
  6. selector.js has the handleProductSelected method set selectedProductId to the evt.detail value.
  7. detail.html has a conditional directive (remember those from Unit 2?) waiting for a product value:
    <template if:true={product}>
  8. detail.js brings the pieces together. It uses the @track decorator to monitor the productId value. Then it uses a get/set pattern to get the value and set it to a local variable product which lets detail.html load the conditional content.

Getters and setters are a common JavaScript construction. They allow you to add logic and conditions to property assignments.

import { LightningElement, track, api } from 'lwc';
import { bikes } from 'c/data';


export default class Detail extends LightningElement {

    // Ensure changes are reactive when product is updated
    @track product;

    // Private var to track @api productId
    _productId = undefined;

    // Use set and get to process the value every time it's
    // requested while switching between products
    set productId(value) {
        this._productId = value;
        this.product = bikes.find(bike => bike.fields.Id.value === value);
    }
    
    // getter for productId
    @api get productId(){
        return this._productId;
    }
}
Each time you click a tile, this process repeats itself.
Note

Note

Events have properties to manage the propagation of the event up the DOM tree. You can read more about them in Configure Event Propagation. Changing the default values is for advanced event handling, and requires testing to ensure the expected behavior.

Deploy Your Files to Your Org

Let’s deploy these files to your Dev Hub enabled org to see how it works. Using the same steps you performed in the last unit, deploy your new files, open the org and create a new page in Lightning App Builder with this app.
  1. From the ebikes-lwc directory, deploy the new files using the username for your Dev Hub org.
    sfdx force:source:deploy -p force-app -u <username>
  2. Open your org
    sfdx force:org:open -u <username> 
  3. In Setup, enter Lightning App Builder in the Quick Find box and then select Lightning App Builder.
  4. Click New.
  5. Create a one region page using the selector component.

You’ve got a fully interactive page composed of several components working together. Next, we experiment with styling and getting live data from an org.

retargeting