Imperative Apex Calls
While@wire is declarative and caches results, imperative Apex calls give you full control over when and how data is fetched.
Basic Imperative Call
import { LightningElement } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
export default class ImperativeApex extends LightningElement {
accounts = [];
error;
isLoading = false;
async loadAccounts() {
this.isLoading = true;
try {
this.accounts = await getAccounts();
this.error = undefined;
} catch (error) {
this.error = error;
this.accounts = [];
this.dispatchEvent(
new ShowToastEvent({
title: 'Error loading accounts',
message: error.body.message,
variant: 'error'
})
);
} finally {
this.isLoading = false;
}
}
handleRefresh() {
this.loadAccounts();
}
}
Imperative with Parameters
import searchAccounts from '@salesforce/apex/AccountController.searchAccounts';
export default class SearchAccounts extends LightningElement {
searchResults = [];
searchTerm = '';
handleSearchChange(event) {
this.searchTerm = event.target.value;
}
async handleSearch() {
if (this.searchTerm.length < 2) {
return;
}
try {
this.searchResults = await searchAccounts({
searchTerm: this.searchTerm
});
} catch (error) {
console.error('Search error:', error);
this.searchResults = [];
}
}
}
Refreshing @wire Data
Combine@wire for initial load with imperative calls for refresh:
import { LightningElement, wire } from 'lwc';
import { refreshApex } from '@salesforce/apex';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';
export default class RefreshWireData extends LightningElement {
wiredAccountsResult;
@wire(getAccounts)
wiredAccounts(result) {
this.wiredAccountsResult = result;
}
get accounts() {
return this.wiredAccountsResult.data;
}
async handleRefresh() {
// Refresh the @wire data
await refreshApex(this.wiredAccountsResult);
}
}
Dynamic Navigation
Use the Navigation Service to navigate to different pages programmatically.Navigate to Record Page
import { LightningElement } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
export default class NavigateToRecord extends NavigationMixin(LightningElement) {
navigateToAccount(accountId) {
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: accountId,
objectApiName: 'Account',
actionName: 'view'
}
});
}
navigateToEdit(accountId) {
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: accountId,
objectApiName: 'Account',
actionName: 'edit'
}
});
}
}
Navigate to List View
navigateToListView() {
this[NavigationMixin.Navigate]({
type: 'standard__objectPage',
attributes: {
objectApiName: 'Contact',
actionName: 'list'
},
state: {
filterName: 'Recent'
}
});
}
Navigate to Related List
navigateToRelatedList(accountId) {
this[NavigationMixin.Navigate]({
type: 'standard__recordRelationshipPage',
attributes: {
recordId: accountId,
objectApiName: 'Account',
relationshipApiName: 'Contacts',
actionName: 'view'
}
});
}
Navigate to Component
navigateToCustomComponent() {
this[NavigationMixin.Navigate]({
type: 'standard__component',
attributes: {
componentName: 'c__MyLightningComponent'
}
});
}
Navigate to External URL
navigateToWebPage() {
this[NavigationMixin.Navigate]({
type: 'standard__webPage',
attributes: {
url: 'https://www.salesforce.com'
}
});
}
Generate URL for Navigation
import { NavigationMixin } from 'lightning/navigation';
export default class GenerateUrl extends NavigationMixin(LightningElement) {
recordPageUrl;
connectedCallback() {
this[NavigationMixin.GenerateUrl]({
type: 'standard__recordPage',
attributes: {
recordId: '001xxxxxxxxxxxx',
actionName: 'view'
}
}).then(url => {
this.recordPageUrl = url;
});
}
}
Dynamic Component Creation
Create components dynamically at runtime.Template-based Dynamic Components
import { LightningElement } from 'lwc';
export default class DynamicComponents extends LightningElement {
selectedComponent = 'chart';
get isChart() {
return this.selectedComponent === 'chart';
}
get isTable() {
return this.selectedComponent === 'table';
}
handleComponentChange(event) {
this.selectedComponent = event.target.value;
}
}
<template>
<lightning-combobox
label="Select View"
value={selectedComponent}
options={options}
onchange={handleComponentChange}
></lightning-combobox>
<template lwc:if={isChart}>
<c-chart-component></c-chart-component>
</template>
<template lwc:if={isTable}>
<c-table-component></c-table-component>
</template>
</template>
Real-Time Updates with Platform Events
Subscribe to platform events for real-time data updates.import { LightningElement } from 'lwc';
import { subscribe, unsubscribe, onError } from 'lightning/empApi';
export default class PlatformEventSubscriber extends LightningElement {
channelName = '/event/Order_Event__e';
subscription = {};
receivedEvents = [];
connectedCallback() {
this.registerErrorListener();
this.handleSubscribe();
}
disconnectedCallback() {
this.handleUnsubscribe();
}
registerErrorListener() {
onError(error => {
console.error('EMP API error:', error);
});
}
async handleSubscribe() {
const messageCallback = (response) => {
console.log('New event received:', response);
this.receivedEvents = [...this.receivedEvents, response];
};
this.subscription = await subscribe(
this.channelName,
-1,
messageCallback
);
}
async handleUnsubscribe() {
await unsubscribe(this.subscription);
}
}
Debouncing User Input
Prevent excessive API calls by debouncing user input.import { LightningElement } from 'lwc';
export default class DebouncedSearch extends LightningElement {
searchTerm = '';
delayTimeout;
handleSearchChange(event) {
const searchTerm = event.target.value;
// Clear previous timeout
clearTimeout(this.delayTimeout);
// Set new timeout
this.delayTimeout = setTimeout(() => {
this.searchTerm = searchTerm;
this.performSearch();
}, 300); // 300ms delay
}
async performSearch() {
if (this.searchTerm.length < 2) {
return;
}
// Perform search
console.log('Searching for:', this.searchTerm);
}
}
Polling for Updates
Periodically check for data updates.import { LightningElement } from 'lwc';
import getJobStatus from '@salesforce/apex/JobController.getJobStatus';
export default class PollingComponent extends LightningElement {
jobId;
jobStatus = 'Pending';
pollInterval;
connectedCallback() {
// Start polling every 5 seconds
this.pollInterval = setInterval(() => {
this.checkJobStatus();
}, 5000);
}
disconnectedCallback() {
// Clean up interval
if (this.pollInterval) {
clearInterval(this.pollInterval);
}
}
async checkJobStatus() {
try {
const status = await getJobStatus({ jobId: this.jobId });
this.jobStatus = status;
// Stop polling if job is complete
if (status === 'Completed' || status === 'Failed') {
clearInterval(this.pollInterval);
}
} catch (error) {
console.error('Error checking job status:', error);
}
}
}
Progress Indicators
Show progress for long-running operations.import { LightningElement } from 'lwc';
export default class ProgressIndicator extends LightningElement {
isLoading = false;
progress = 0;
maxSteps = 5;
async performMultiStepOperation() {
this.isLoading = true;
this.progress = 0;
for (let step = 1; step <= this.maxSteps; step++) {
await this.performStep(step);
this.progress = (step / this.maxSteps) * 100;
}
this.isLoading = false;
}
async performStep(stepNumber) {
// Simulate async operation
return new Promise(resolve => {
setTimeout(() => {
console.log(`Step ${stepNumber} complete`);
resolve();
}, 1000);
});
}
}
<template>
<template lwc:if={isLoading}>
<lightning-progress-bar
value={progress}
size="large"
></lightning-progress-bar>
</template>
<lightning-button
label="Start Process"
onclick={performMultiStepOperation}
disabled={isLoading}
></lightning-button>
</template>
Best Practices
Use @wire for Initial Load, Imperative for Actions
// Use @wire for initial data load
@wire(getAccounts)
accounts;
// Use imperative for user-triggered actions
async handleDelete(accountId) {
await deleteAccount({ accountId });
// Refresh @wire data
refreshApex(this.accounts);
}
Handle Errors Gracefully
async loadData() {
try {
this.data = await getData();
} catch (error) {
this.handleError(error);
}
}
handleError(error) {
let message = 'Unknown error';
if (error.body && error.body.message) {
message = error.body.message;
}
this.dispatchEvent(
new ShowToastEvent({
title: 'Error',
message: message,
variant: 'error'
})
);
}
Clean Up Resources
disconnectedCallback() {
// Clear intervals
if (this.pollInterval) {
clearInterval(this.pollInterval);
}
// Clear timeouts
if (this.delayTimeout) {
clearTimeout(this.delayTimeout);
}
// Unsubscribe from events
if (this.subscription) {
unsubscribe(this.subscription);
}
}
