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
| Decorator | Purpose |
|---|---|
@api | Public property/method — set by the parent |
@wire | Reactive data from Apex or Lightning Data Service |
@track | Deep 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
@wirevs imperative Apex? → Wire: reactive, cached, read-only. Imperative: on demand, can do DML.- Why
cacheable=true? → Required for wire; enables client caching; no DML allowed. - How does a child talk to its parent? →
CustomEvent; parent → child via@apiproperties/methods.