1. LWC Architecture
Component-Based Architecture
- What: LWC uses web standards (Custom Elements, Shadow DOM) to create reusable components.
- Example:
<!-- childComponent.html -->
<template>
<div class="child">Child Component: {message}</div>
</template>
// childComponent.js
import { LightningElement, api } from 'lwc';
export default class ChildComponent extends LightningElement {
@api message = 'Hello from child!';
}
Lifecycle Hooks
constructor()
: Initialize state (avoid touching DOM).
connectedCallback()
: Component inserted into DOM. Fetch data here.
render()
: Render the template (rarely overridden).
renderedCallback()
: After rendering. Use for DOM manipulation.
disconnectedCallback()
: Cleanup (e.g., remove event listeners).
import { LightningElement } from 'lwc';
export default class LifecycleDemo extends LightningElement {
constructor() {
super();
console.log('Constructor called');
}
connectedCallback() {
console.log('Component connected to DOM');
}
renderedCallback() {
console.log('Component rendered');
}
}
2. Data Binding & Decorators
@api
(Public Property/Method)
- Purpose: Expose properties/methods to parent components.
- Example:
// child.js
@api itemId;
@api handleClick() { /* logic */ }
<!-- parent.html -->
<c-child item-id="123" onbuttonclick={handleClick}></c-child>
@track
(Reactive Properties)
- Purpose: Track changes to object/array properties (primitives are reactive by default).
- Example:
@track user = { name: 'John', age: 30 };
updateUser() {
this.user.name = 'Jane'; // Triggers re-render
}
@wire
(Reactive Data Fetch)
- Purpose: Fetch data from Apex or Lightning Data Service (LDS).
- Example:
import { getRecord } from 'lightning/uiRecordApi';
// Wire to LDS
@wire(getRecord, { recordId: '$recordId', fields: ['Account.Name'] })
account;
// Wire to Apex (Apex method must be cacheable)
import getContacts from '@salesforce/apex/ContactController.getContacts';
@wire(getContacts, { accountId: '$recordId' })
wiredContacts({ error, data }) {
if (data) {
/* handle data */
}
}
3. Component Communication
Parent → Child
- Use
@api
to pass data or call methods.
- Example:
<!-- parent.html -->
<c-child message="Hello Parent!"></c-child>
Child → Parent (Custom Events)
- Dispatch events from child, handle in parent.
- Example:
// child.js
handleClick() {
this.dispatchEvent(new CustomEvent('notify', { detail: 'Data from child' }));
}
<!-- parent.html -->
<c-child onnotify={handleNotification}></c-child>
// parent.js
handleNotification(event) {
console.log(event.detail); // "Data from child"
}
Cross-Component (Lightning Message Service)
// Component A (publish)
import { publish, MessageContext } from 'lightning/messageService';
export default class Publisher extends LightningElement {
@wire(MessageContext) messageContext;
handlePublish() {
const payload = { recordId: '001xx...' };
publish(this.messageContext, CHANNEL, payload);
}
}
// Component B (subscribe)
import { subscribe, MessageContext } from 'lightning/messageService';
export default class Subscriber extends LightningElement {
@wire(MessageContext) messageContext;
subscription;
connectedCallback() {
this.subscription = subscribe(
this.messageContext,
CHANNEL,
(payload) => { /* handle payload */ }
);
}
}
4. Apex Integration
Reactive (@wire
) vs. Imperative Calls
@wire(getContacts, { accountId: '$recordId' }) contacts;
- Imperative (for DML operations):
import updateContact from '@salesforce/apex/ContactController.updateContact';
handleSave() {
updateContact({ contact: this.contact })
.then(() => { /* success */ })
.catch(error => { /* handle error */ });
}
Apex Method Structure:
// ContactController.cls
@AuraEnabled(cacheable=true)
public static List<Contact> getContacts(String accountId) {
return [SELECT Id, Name FROM Contact WHERE AccountId = :accountId];
}
5. Lightning Data Service (LDS)
getRecord
and updateRecord
import { getRecord, updateRecord } from 'lightning/uiRecordApi';
// Fetch data
@wire(getRecord, { recordId: '$recordId', fields: FIELDS }) account;
// Update data
async handleSave() {
const fields = { ... };
await updateRecord({ fields });
}
6. Security
Lightning Web Security (LWS)
- Purpose: Replace Locker Service to enforce security boundaries.
- Example: Sanitize HTML to prevent XSS:
import { sanitizeHtml } from 'lightning/utilsPrivate';
const unsafeHtml = '<script>alert("XSS")</script>';
const safeHtml = sanitizeHtml(unsafeHtml); // Sanitizes the HTML
7. Testing (Jest)
Test a Component:
// myComponent.test.js
import { createElement } from 'lwc';
import MyComponent from 'c/myComponent';
test('displays greeting', () => {
const element = createElement('c-my-component', { is: MyComponent });
document.body.appendChild(element);
const div = element.shadowRoot.querySelector('div');
expect(div.textContent).toBe('Hello, World!');
});
8. Debugging & Deployment
Debugging:
- Use Chrome DevTools (Components and Console tabs).
console.log(this.property)
to track reactive values.
Deployment:
sfdx force:source:deploy -p ./force-app/main/default/lwc
sfdx force:org:open
Example Scenario: Search with Debounce
<!-- searchComponent.html -->
<template>
<lightning-input
type="search"
onchange={handleSearch}
label="Search"
></lightning-input>
</template>
// searchComponent.js
import { LightningElement } from 'lwc';
export default class SearchComponent extends LightningElement {
timer;
handleSearch(event) {
clearTimeout(this.timer);
const searchTerm = event.target.value;
// Debounce to avoid excessive Apex calls
this.timer = setTimeout(() => {
this.dispatchEvent(new CustomEvent('search', { detail: searchTerm }));
}, 300);
}
}
Key Takeaways
- Modularity: Break down components for reusability.
- Reactiveness: Use decorators (
@track
, @wire
) wisely.
- Events: Master parent-child and cross-component communication.
- Security: Sanitize inputs and follow LWS guidelines.