Lightning Web Components – Calling Apex from LWC Questions
Integrating Apex with Lightning Web Components is a fundamental skill for Salesforce developers, enabling powerful server-side processing and data manipulation. These questions cover the two primary methods of calling Apex (@wire and imperative calls), error handling, complex parameter passing, caching strategies, and best practices for efficient data retrieval and manipulation. Mastering these concepts will help you build responsive and data-rich LWC applications.
Calling Apex from LWC - Q&A
- Q1. What are the two ways to call Apex from LWC?
Ans: Two primary methods: 1. @wire Service (Reactive/Proactive)@wire(getRecord, { recordId: '$recordId' }) wiredRecord({ error, data }) { if(data) { /* handle data */ } if(error) { /* handle error */ } }
2. Imperative Calls (Direct invocation)import getAccounts from '@salesforce/apex/AccountController.getAccounts'; getAccounts({ param: value }) .then(result => { /* handle result */ }) .catch(error => { /* handle error */ });
- Q2. When would you use @wire vs imperative Apex calls?
Ans: @wire is best for: - Reactive data binding (automatic refresh) - Cacheable data (with @AuraEnabled(cacheable=true)) - Read-only operations Imperative is better for: - DML operations - Conditional/non-reactive calls - Complex parameter handling - Q3. How do you handle errors in Apex calls?
Ans: Comprehensive error handling:// @wire error handling @wire(getRecord, { recordId: '$recordId' }) wiredRecord({ error, data }) { if(error) { this.error = reduceErrors(error); // Helper function } } // Imperative error handling getAccounts() .catch(error => { this.error = error.body.message; logErrorToServer(error); // Additional logging });
- Q4. What is the reduceErrors pattern in LWC?
Ans: Standard error processing:// utils/errorUtils.js export function reduceErrors(errors) { return ( (Array.isArray(errors) ? errors : [errors]) .map(error => { // UI API errors if (error.body && Array.isArray(error.body.fieldErrors)) { return error.body.fieldErrors.join(', '); } // Apex errors else if (error.body && error.body.message) { return error.body.message; } return error.message || 'Unknown error'; }) .join(', ') ); }
- Q5. How do you pass complex objects to Apex?
Ans: JSON serialization pattern:// LWC const filters = { region: 'NA', status: 'Active' }; getAccounts({ filters: JSON.stringify(filters) }); // Apex @AuraEnabled public static List
getAccounts(String filtersJSON) { Map filters = (Map ) JSON.deserializeUntyped(filtersJSON); // Use filters map } - Q6. What are cacheable methods and their limitations?
Ans: @AuraEnabled(cacheable=true): - Enables client-side caching - Required for @wire calls - Limitations: * No DML allowed * Must be idempotent * No session state modification Example:@AuraEnabled(cacheable=true) public static List
getAccounts() { return [SELECT Id, Name FROM Account]; } - Q7. How do you refresh @wire data?
Ans: Refresh strategies: 1. Reactive parameters (when '$param' changes) 2. refreshApex() for manual refresh:import { refreshApex } from '@salesforce/apex'; // Store provisioned value wiredAccountsResult; @wire(getAccounts) wiredAccounts(result) { this.wiredAccountsResult = result; } handleRefresh() { refreshApex(this.wiredAccountsResult); }
- Q8. How do you call Apex from LWC tests?
Ans: Mocking patterns:// Mock Apex import import getAccounts from '@salesforce/apex/AccountController.getAccounts'; // Test setup jest.mock( '@salesforce/apex/AccountController.getAccounts', () => { return { default: jest.fn() }; }, { virtual: true } ); // Test case it('loads accounts', () => { getAccounts.mockResolvedValue([{ Id: '1', Name: 'Test' }]); // Create and test component });
- Q9. How do you implement pagination with Apex?
Ans: Pagination pattern:// Apex @AuraEnabled(cacheable=true) public static PaginationResult getAccountsPage( Integer offset, Integer pageSize ) { return new PaginationResult( [SELECT Id, Name FROM Account LIMIT :pageSize OFFSET :offset], [SELECT COUNT() FROM Account] ); } // LWC loadMore() { getAccountsPage({ offset: this.records.length, pageSize: this.pageSize }).then(result => { this.records = [...this.records, ...result.data]; this.total = result.total; }); }
- Q10. How do you handle long-running Apex operations?
Ans: Patterns for long operations: 1. Queueable chaining:@AuraEnabled public static Id startLongProcess(String input) { return System.enqueueJob(new LongProcessQueueable(input)); }
2. Polling from LWC:// LWC checkStatus(processId) { return new Promise((resolve) => { const interval = setInterval(() => { getStatus({ processId }) .then(status => { if(status === 'Completed') { clearInterval(interval); resolve(); } }); }, 2000); }); }
- Q11. How do you implement search-as-you-type with Apex?
Ans: Debounced search pattern:// LWC searchTerm; debounceTimeout; handleSearch(event) { clearTimeout(this.debounceTimeout); this.debounceTimeout = setTimeout(() => { this.searchTerm = event.target.value; }, 300); // 300ms delay } @wire(searchAccounts, { searchTerm: '$searchTerm' }) wiredAccounts;
- Q12. How do you pass record IDs to Apex securely?
Ans: Security best practices: - Always validate IDs in Apex:@AuraEnabled public static Account getAccount(Id accountId) { // Validate ID format if(!accountId.getSObjectType() == Account.sObjectType) { throw new AuraHandledException('Invalid ID'); } return [SELECT Name FROM Account WHERE Id = :accountId]; }
- Use with sharing in Apex classes - Q13. How do you handle DML operations in LWC?
Ans: Imperative DML pattern:// Apex @AuraEnabled public static Id saveAccount(Account account) { upsert account; return account.Id; } // LWC handleSave() { saveAccount({ account: this.draftValues[0] }) .then(accountId => { refreshApex(this.accounts); }); }
- Q14. How do you implement file uploads with Apex?
Ans: File upload pattern:// LWC
handleUploadFinished(event) { const files = event.detail.files; saveFile({ file: files[0] }); // Imperative Apex } // Apex @AuraEnabled public static Id saveFile(String fileData, String fileName) { ContentVersion cv = new ContentVersion( VersionData = EncodingUtil.base64Decode(fileData), Title = fileName, PathOnClient = fileName ); insert cv; return cv.Id; } - Q15. How do you test @wire adapters?
Ans: Wire adapter testing:// Mock @wire response import { registerLdsTestWireAdapter } from '@salesforce/sfdx-lwc-jest'; import getRecord from '@salesforce/apex/getRecord'; const mockGetRecord = registerLdsTestWireAdapter(getRecord); it('displays record data', () => { // Create component mockGetRecord.emit(mockData); // Verify rendering return Promise.resolve().then(() => { expect(element.shadowRoot.textContent).toContain('Test Account'); }); });
- Q16. How do you implement infinite scrolling?
Ans: Infinite scroll pattern:// LWC connectedCallback() { window.addEventListener('scroll', this.handleScroll.bind(this)); } handleScroll() { if(window.innerHeight + window.scrollY >= document.body.offsetHeight) { this.loadMore(); } } loadMore() { getMoreAccounts({ offset: this.accounts.length }) .then(newAccounts => { this.accounts = [...this.accounts, ...newAccounts]; }); }
- Q17. How do you handle platform events in LWC?
Ans: Platform event pattern:// Apex @AuraEnabled public static void publishEvent(String message) { EventBus.publish(new Notification_Event__e( Message__c = message )); } // LWC import { subscribe, unsubscribe, onError } from 'lightning/empApi'; subscription; connectedCallback() { subscribe('/event/Notification_Event__e', -1, (event) => { this.notification = event.data.payload.Message__c; }).then(response => { this.subscription = response; }); } disconnectedCallback() { unsubscribe(this.subscription); }