Lightning Web Components is the new programming model that Salesforce released early this year. If you don’t know what it is about, check this post I published some time ago.
One of the keys to learn how Lightning Web Components work is to understand how components can communicate between each other. The communication patterns are different from Aura, and are one of the culprits of Lightning Web Components superb performance.
To talk about communication, we first need to talk about composition. Lightning Web Components can be composed in a similar way to Aura components. You just need to reference the child component in the markup of the parent component. Some things change:
- The character that separates the namespace from the component name must be a dash.
- The component name must be indicated in kebab case. This means that if the component name is something like myCamelCaseName, the reference in markup will be my-camel-case-name. This change has been done to follow web standards.
- The ending tag is compulsory, you cannot omit it.
Here you have an example in which we are instantiating a child component called childComponent:
<template> <c-child-component></c-child-component> </template>
The data can flow in two directions, from parent to child (down) or from child to parent (up). There is also a special case in which components are siblings. Let’s dive into this!
Passing data down
To pass data down in the component hierarchy, the child component must declare a public API, the contract through which a parent component can pass information. What forms the public API of a component are their public properties and public methods.
Public properties are properties annotated with the @api decorator. Decorators are not more than functions that are applied to other entities, as a property or a function, to add some additional capabilities. It is a pattern that can be implemented in multiple languages. In this case, the @api decorator is making the property available to be set via an attribute (same name) on component instantiation.
In the following example, we are defining a public property and assigning a default value to it.
You can assign a default value to a public property, but, you should never change the value of a public property in the component itself. Remember the purpose of this property is to be the public API of the component, so, only the parent component should set or change its value.
import { LightningElement, api } from 'lwc'; export default class ChildComponent extends LightningElement { @api myProperty = 'Default value for my property'; }
We can then set a value for that property passing in a value from the parent component in this way:
<template> <c-child-component my-property="Value passed in"></c-child-component> </template>
This value does not need to be a static value, and could be a property defined in the parent component. In this case we would use braces to assign the property value. Each time the parent property value changes, the value will be passed in to the child public property.
<template> <c-child-component my-property={parentComponentProperty}></c-child-component> </template>
Public methods are methods annotated with the @api decorator. Public methods are part of the public API of the component, and can be called from a parent component, as they will be exposed as callable functions attached to the component DOM element API.
An example of public method would be:
import { LightningElement, api } from 'lwc'; export default class ChildComponent extends LightningElement { @api doWhatever(param1) { // Do whatever... return 'Finished!'; } }
As you can see, public methods can receive parameters and return values.
The method will be surfaced in the DOM element API, and we can call it from the parent in the next way:
import { LightningElement } from 'lwc'; export default class ParentComponent extends LightningElement { ... const returnValue = this.template.querySelector('c-child-component').doWhatever('My param'); ... }
If there are slots in place, things change a bit, but slots are out of the scope of this blog.
Passing data up
Data must be passed up using Events. Events are just standard JavaScript events that we fire from the child component and listen into the parent.
Events are created using the Event or CustomEvent standard JavaScript classes. The only difference between them is that the CustomEvent class will allow you to store information in its detail property and thus transmit that information to the listeners of the event. Then, you can make an event target dispatch the created event invoking the dispatchEvent standard method.
This is an example of how to create and fire an event:
import { LightningElement } from 'lwc'; export default class ChildComponent extends LightningElement { … const myEvent = new CustomEvent('eventname', { detail: { prop1: value } }); this.dispatchEvent(myEvent); … }
To listen to an event, we have to attach a handler. Event handlers are not more than functions that receive the event as parameter and react to it. As an example:
import { LightningElement } from 'lwc'; export default class ParentComponent extends LightningElement { handleEvent(event) { // Do whatever with event.detail } }
Handlers can be attached statically or dynamically. For simplicity, let’s focus on the static case, in which we attach the handler assigning it to an attribute named ‘on’, plus the name of the event.
<template> <c-child-component oneventname={handleEvent}></c-child-component> </template>
Passing data to siblings
To pass data to sibling components, those that are at the same level, for example in a page created with app builder, the recommendation is to use a singleton library that follows the publish – subscribe pattern.
Salesforce has published a service component (components that only have JavaScript and no html) called pubsub that follows this pattern, and you can include it in your developments.
Note that this is the kind of mechanism that will be needed when migrating application events from Aura.
Conclusions
One of the keys to understand how Lightning Web Components work is to learn how components communicate with each other.
This is quite different from Aura, as bidirectional data binding doesn’t exist anymore, and information must be passed up through events. This change is one of the reasons because of which performance in Lightning Web Components has drastically improved.
Additionally, this pattern makes easier to develop and debug components, as it is simpler to detect which component changed a property value. Only parent components can change public properties, and only child components can change private / tracked properties. But let’s leave the discussion about tracked properties for another ocassion 🙂
Leave a Reply