Data Binding & Events in LWC

Lightning Web Components use one-way data binding and custom events to connect parent and child components. This guide covers how to pass data, receive updates, and bubble events.

1. Passing Data via @track and @api

Parent Component


// parentComponent.js
import { LightningElement, track } from 'lwc';

export default class ParentComponent extends LightningElement {
    @track accountName = 'Acme Corp';
    @track isLoading = false;

    handleNameChange(event) {
        this.accountName = event.target.value;
    }
}
  

<!-- parentComponent.html -->
<template>
    <lightning-input 
        type="text" 
        label="Account Name"
        value={accountName}
        onchange={handleNameChange}>
    </lightning-input>

    <!-- Pass data to child -->
    <c-child-component 
        account-name={accountName}
        is-loading={isLoading}>
    </c-child-component>
</template>
  

Child Component


// childComponent.js
import { LightningElement, api, track } from 'lwc';

export default class ChildComponent extends LightningElement {
    @api accountName;  // Receives from parent
    @api isLoading;

    @track displayName = '';

    connectedCallback() {
        // Update display when parent data arrives
        if (this.accountName) {
            this.displayName = this.accountName.toUpperCase();
        }
    }

    // This is called when @api property changes
    displayAccountName() {
        return `Account: ${this.accountName}`;
    }
}
  

<!-- childComponent.html -->
<template>
    <div class="card">
        <h3>{displayAccountName()}</h3>
        <p>Status: <span if:true={isLoading}>Loading...</span></p>
    </div>
</template>
  

2. Calling Child Methods from Parent

Use template refs to call methods on child components:

Parent Component


// parentComponent.js
import { LightningElement } from 'lwc';

export default class ParentComponent extends LightningElement {
    handleRefresh() {
        // Get reference to child component
        const childComponent = this.template.querySelector('c-child-component');
        
        // Call child method
        childComponent.refresh();
    }

    handleResetForm() {
        const childComponent = this.template.querySelector('c-child-component');
        childComponent.resetForm();
    }
}
  

<!-- parentComponent.html -->
<template>
    <lightning-button 
        label="Refresh Data" 
        onclick={handleRefresh}>
    </lightning-button>

    <lightning-button 
        label="Reset Form" 
        onclick={handleResetForm}>
    </lightning-button>

    <c-child-component></c-child-component>
</template>
  

Child Component


// childComponent.js
import { LightningElement, track } from 'lwc';

export default class ChildComponent extends LightningElement {
    @track data = [];
    @track formData = {};

    // Method called from parent
    refresh() {
        this.loadData();
    }

    // Method called from parent
    resetForm() {
        this.formData = {};
        this.template.querySelector('form').reset();
    }

    loadData() {
        // Simulate data fetch
        this.data = [
            { id: 1, name: 'Item 1' },
            { id: 2, name: 'Item 2' }
        ];
    }
}
  

3. Parent Listening to Child Events

While data flows down via @api, events bubble up from child to parent:

Child Component dispatching event


// childComponent.js
import { LightningElement, api } from 'lwc';

export default class ChildComponent extends LightningElement {
    @api accountId;

    handleSave() {
        // Dispatch custom event to parent
        const event = new CustomEvent('accountsaved', {
            detail: { 
                accountId: this.accountId,
                timestamp: new Date()
            }
        });
        this.dispatchEvent(event);
    }
}
  

Parent Component listening to event


// parentComponent.js
import { LightningElement } from 'lwc';

export default class ParentComponent extends LightningElement {
    handleAccountSaved(event) {
        const { accountId, timestamp } = event.detail;
        console.log(`Account ${accountId} saved at ${timestamp}`);
    }
}
  

<!-- parentComponent.html -->
<template>
    <c-child-component 
        account-id="00100000000001"
        onaccountsaved={handleAccountSaved}>
    </c-child-component>
</template>
  

4. Real Example: Account Editor


// accountListComponent.js
import { LightningElement, track } from 'lwc';

export default class AccountListComponent extends LightningElement {
    @track accounts = [
        { id: '001', name: 'Acme Corp', revenue: 1000000 },
        { id: '002', name: 'Global Tech', revenue: 500000 }
    ];
    
    @track selectedAccountId;

    handleAccountSelect(event) {
        this.selectedAccountId = event.detail;
    }

    handleAccountUpdated(event) {
        const { accountId, name, revenue } = event.detail;
        
        // Update account in list
        const index = this.accounts.findIndex(acc => acc.id === accountId);
        if (index !== -1) {
            this.accounts[index] = { id: accountId, name, revenue };
        }
    }
}
  

<!-- accountListComponent.html -->
<template>
    <div class="container">
        <h2>Accounts</h2>
        <c-account-list-view 
            accounts={accounts}
            onaccountselect={handleAccountSelect}>
        </c-account-list-view>

        <c-account-editor 
            if:true={selectedAccountId}
            account-id={selectedAccountId}
            onaccountupdated={handleAccountUpdated}>
        </c-account-editor>
    </div>
</template>
  

Key Takeaways