Lightning Web Components Basics

Component structure, decorators, calling Apex with @wire and imperatively, events and Lightning Data Service.

Developer 2 min readLWCJavaScriptWireApex

Component files

accountList/
├── accountList.html          ← template
├── accountList.js            ← controller (ES module)
└── accountList.js-meta.xml   ← where it can be used

Decorators

DecoratorPurpose
@apiPublic property/method — set by the parent
@wireReactive data from Apex or Lightning Data Service
@trackDeep reactivity for objects/arrays (fields are reactive by default now)

Apex controller

apex
public with sharing class AccountController {
    @AuraEnabled(cacheable=true)
    public static List<Account> topAccounts(String industry) {
        return [SELECT Id, Name, AnnualRevenue FROM Account
                WHERE Industry = :industry WITH USER_MODE
                ORDER BY AnnualRevenue DESC LIMIT 10];
    }
}

cacheable=true is required for @wire and means the method must not do DML.

Calling Apex with @wire

js
import { LightningElement, api, wire } from 'lwc';
import topAccounts from '@salesforce/apex/AccountController.topAccounts';

export default class AccountList extends LightningElement {
    @api industry = 'Technology';
    @wire(topAccounts, { industry: '$industry' }) accounts;   // re-runs when industry changes
}
html
<template>
    <template lwc:if={accounts.data}>
        <template for:each={accounts.data} for:item="acc">
            <p key={acc.Id}>{acc.Name}</p>
        </template>
    </template>
    <template lwc:elseif={accounts.error}>Something went wrong.</template>
</template>

Imperative Apex call (e.g. on a button click)

js
async handleRefresh() {
    try {
        this.rows = await topAccounts({ industry: this.industry });
    } catch (e) {
        this.error = e.body?.message;
    }
}

Child → parent communication

js
// child
this.dispatchEvent(new CustomEvent('select', { detail: { id: this.recordId } }));
html
<!-- parent -->
<c-account-tile onselect={handleSelect}></c-account-tile>

Lightning Data Service (no Apex needed)

js
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import NAME from '@salesforce/schema/Account.Name';

@wire(getRecord, { recordId: '$recordId', fields: [NAME] }) account;
get name() { return getFieldValue(this.account.data, NAME); }

Base components like lightning-record-form handle create/edit/view with sharing and FLS built in.

Interview questions

  1. @wire vs imperative Apex? → Wire: reactive, cached, read-only. Imperative: on demand, can do DML.
  2. Why cacheable=true? → Required for wire; enables client caching; no DML allowed.
  3. How does a child talk to its parent? → CustomEvent; parent → child via @api properties/methods.