html code

Utilizing Decorators in Lightning Web Components

How can decorators be employed in Lightning Web Components? I aim to enhance my skills in Lightning Web Components (LWC) and develop a course within the coming months. While exploring LWC, aside from realizing that kebab case isn’t related to food, decorators stood out as another concept that intrigued me. I’ve used them, but I haven’t grasped their full essence.

Nevertheless, I’ll be discussing decorators because I’m determined to gain a comprehensive understanding of this subject through this tutorial.

What do decorators entail in LWC?

Decorators are just wrappers around a function. For instance, they are used to enhance the functionality of the function without modifying the underlying function. (See Higher Order Function)

The ability to create decorators is part of ECMAScript. Decorators are very widely used in JavaScript through transpilers. For example, see the documentation of core-decoratorsember-decoratorsAngularStencil, and MobX decorators.

These three decorators are unique to Lightning Web Components (lwc).

  • @api – makes a property public.
  • @track – makes property private
  • @wire – reads Salesforce data or metadata

What do Reactive Properties entail

If the value of properties changes, the component rerenders. To make the property reactive we wrap them with such decorators.

Properties that are non-reactive means changes in value will not cause the component to rerender.

When is it appropriate to utilize @api decorators

  • when you want to make the property reactive and be accessible outside of the component they were declared.
  • the parent component or owner component can set values to the public properties for the child component.
  • if we want changes in the property value to rerender any content on components that references the property.

import syntax

import {LightningElement, api} from 'lwc';

course.js – decorating a property

import {LightningElement, api} from 'lwc';
export default class Course extends LightningElement {
   @api courseTitle;
}

course.html – template use

<template>
   <div>{courseTitle}</div>
</template>

courseApp.html – parent component has access and can set child properties

<template>
   <div>
     <c-course course-title="Learning Lightning Web Components"></c-course>
     <c-course course-title="Learning Decorators"></c-course>
   </div>
</template>

At what instances should the @track decorators be employed

  • when you want to make a property reactive but only within the component.
  • we don’t want the parent component to change the property values of the child component.

import syntax

import {LightningElement, track} from 'lwc';

course.js – added new track property

import {LightningElement, api, track} from 'lwc';
export default class Course extends LightningElement {
   @api courseTitle;
   @track courseLevel = 'Beginner';
}

course.html – added new template

<template>
    <div>{courseTitle} - {courseLevel}</div>
 </template>

courseApp.html – parent setting the course-level to Intermediate has no effect

<template>
    <div>
      <c-course course-title="Learning Lightning Web Components" course-level="Intermediate"></c-course>
      <c-course course-title="Learning Decorators" course-level="Intermediate"></c-course> 
    </div>
 </template>

In what situations should the @wire decorators be utilized

  • used to read Salesforce data or metadata.
  • we want the component to rerender (be reactive) when the wire service provisions data.

To use the @wire decorator you need to understand a bit about the wire service and calling apex methods

What is the wire service?

  • the wire service provisions an immutable stream of data to the component
    • immutable means it is read-only data. To mutate it you need to make a shallow copy of the object.
  • the wire service delegates control flow to the LWC engine. This is great for read operations. But if you want to do C-U-D(create, update or delete) we need to use the Javascript API instead.
  • the wire service provisions data either from the client cache if it exists, then fetches from the server if needed.

The wire service syntax and steps

  • import from adapterModule and create identifier for the wire adapter
  • decorate wire property or function with the wire adapterConfig object
  • create a property or function that receives the stream of data
import {adapterId} from 'adapterModule';
@wire(adapterId, adapterConfig)
propertyOrFunction;

Importing objects, fields and relationships:

Use the lightning\ui* api wire adapter module and import reference to objects and fields to make sure the object or fields exists. Recommended using the lightning\ui* api wire adapter module because it respects the user’s CRUD, FLS, and sharing access.

import { getRecord } from 'lightning/uiRecordApi';

Importing objects, fields and relationships

// object name as adapterId
import COURSE_OBJECT from '@salesforce/schema/Course__c';
// field name as adapterid
import COURSE_TITLE_FIELD from '@salesforce/schema/Course__c.Title__c';
// field relationship
import COURSE_TEACHER_REL_NAME_FIELD from '@salesforce/schema/Course__c.Teacher__r.Name';

@wire decorating a property

import { LightningElement, api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import COURSE_TITLE_FIELD from '@salesforce/schema/Course__c.Title__c';

export default class Course extends LightningElement{ 
  @api recordId;

  @wire(getRecord, {recordId: '$recordId', fields: [COURSE_TITLE_FIELD]})
  record;
}

@wire decorating a function

import { LightningElement, api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import COURSE_TITLE_FIELD from '@salesforce/schema/Course__c.Title__c';

export default class Course extends LightningElement{ 
  @api recordId;
  @track record;
  @track error;

  @wire(getRecord, {recordId: '$recordId', fields: [COURSE_TITLE_FIELD]})
  wiredAccount({ error, data }) {
      if (data) {
          this.record = data;
          this.error = undefined;
      } else if (error) {
          this.error = error;
          this.record = undefined;
      }
  }
}

What’s the procedure for employing wire service alongside Apex methods

  • Apex methods can be called via @wire or imperatively. It follows a similar syntax but instead of importing the wire adapter, we import the class method. Then data streamed will be received either by a property or a function.wire adapter provisions new values when available, wire service provisioned data from apex methods doesn’t. To get the new values we need to call refreshApex().

import syntax

import apexMethodName from '@salesforce/apex/Namespace.Classname.apexMethodReference';

expose Apex methods with static/global or public and annotate with @AuraEnabled(cacheable=true) – improves runtime performance

public with sharing class CourseController {
    @AuraEnabled(cacheable=true)
    public static List<Course__c> getCourseListList() {
        return [
            SELECT Id, Title__c FROM Course__c
            WITH SECURITY_ENFORCED
            LIMIT 10
        ];
    }
}

import the apex method and wire the property or function which will receive the data stream

// wiredserviceapex.js
import { LightningElement, wire } from 'lwc';
import getCourseList from '@salesforce/apex/CourseController.getCourseList';

export default class WiredServiceApex extends LightningElement {
    @wire(getCourseList) courses;
}

if you used @wire, Apex method provisioned data can get stale, use the refreshApex() to get the latest values

importing syntax

import { refreshApex } from '@salesforce/apex';

usage example – via button click refresh teh data that the wire service provisioned

// wiredserviceapex.js
import { LightningElement, wire } from 'lwc';
import getCourseList from '@salesforce/apex/CourseController.getCourseList';
import { refreshApex } from '@salesforce/apex';

export default class WiredServiceApex extends LightningElement {
    @wire(getCourseList) courses;
    
    // Refresh Apex data that the wire service provisioned
    handler() { 
      refreshApex(this.courses);
    }
}

Well, that wraps the basics on decorators. Hope you learned something from this post and found it useful.