You can add components within the body of another component. Composition enables you to build complex components from simpler building-block components.
Composing apps and components from a set of smaller components make code more reusable and maintainable.
Owner Vs Container
Owner
The owner is the component that owns the template. An owner can:
Set public properties on composed components
Call public methods on composed components
Listen for events dispatched by composed components
Container
A container contains other components but itself is contained within the owner component. A container is less powerful than the owner. A container can:
Read, but not change, public properties in contained components
Call public methods on composed components
Listen for some, but not necessarily all, events bubbled up by components that it contains.
Parent Vs child
When a component contains another component, which, in turn, can contain other components, we have a containment hierarchy. In the documentation, we sometimes talk about parent and child components. A parent component contains a child component. A parent component can be the owner or a container.
Component Communication In LWC
Now let’s understand how components communicate in LWC. Since we know that lwc components can be nested there are 3 possibilities for communication between components.
Parent to child communication
Child to parent communication
Communication between two separate components.
Parent to child communication
Public property of any child can be accessed through parent components. Use @api decorator to make the field public. Let’s understand all of this with a couple of examples.
Example 1:
Let’s start with a very basic example where the parent component will access child property. First, create the child component (Container) childEx1 which will be used in parentEx1 component.
childEx1.html
<template>
<div>{itemName}</div>
</template>
childEx1.js
The @api decorator exposes the itemName field as a public property.
import { LightningElement,api } from 'lwc';
export default class TodoApp extends LightningElement {
@api itemName;
}
Now let’s create parent or owner components that will call child components.
parentEx1.html
<template>
<c-child-ex1 item-name="Milk"></c-child-ex1>
<c-child-ex1 item-name="Bread"></c-child-ex1>
</template>
You notice here while calling the child component, Property names in JavaScript are in camel case while HTML attribute names are in kebab case (dash-separated) to match HTML standards.
parentEx1.js
Keep default js file
import { LightningElement } from 'lwc';
export default class ParentEx1 extends LightningElement {}
Make the required changes in your metafile of component and add your component to your page
Example 2:
Let's find out other example, there are two component childEx2 and parentEx2 as below:
childEx2.html
<template>
<template if:true={contact}>
<img src={contact.Picture} alt="Profile photo" />
<p>{contact.Name}</p>
<p>{contact.Title}</p>
</template>
<template if:false={contact}>
<p>No contact data available.</p>
</template>
</template>
childEx2.js
import { api, LightningElement } from 'lwc';
export default class ChildEx2 extends LightningElement {
@api contact;
}
parentEx2.html
<template>
<c-child-ex2 contact={contact}></c-child-ex2>
</template>
parentEx2.js
import { LightningElement } from 'lwc';
export default class ParentEx2 extends LightningElement {
contact={
Name: 'Amy Taylor',
Title: 'VP of Engineering',
Picture: 'https://s3-us-west-1.amazonaws.com/sfdc-demo/people/amy_taylor.jpg',
};
}
In both the above examples, we understand how property can set from Parent to child. Let's go with a more realistic example where the parent component will call the method from the child.
To expose a public method, decorate it with @api. Public methods are part of a component’s API. To communicate down the containment hierarchy, owner and parent components can call JavaScript methods (or set properties) on child components.
Example 3:
Consider we have a component called parentEx3 which has a date input asking for your birth date.. Also inside this parentEx3 component, we have another component called childEx3 component which shows a message about your birthday, whenever you select a birthdate in parentEx.
childEx3.html
<template>
<lightning-card title='Child Component'>
{greetings}
</lightning-card>
</template>
childEx3.js
import { api, LightningElement } from 'lwc';
export default class ChildEx3 extends LightningElement {
greetings;
@api
handleBday(bdayDate){
this.greetings = 'Hey There Your Bday On ' + bdayDate;
}
}
parentEx3.html
<template>
<lightning-card title='Parent Card'>
<lightning-input type="date" name="input1" label="Select Your Birthdate" onchange={passBday}></lightning-input>
</lightning-card>
<lightning-card title='Parent call child'>
<c-child-ex3></c-child-ex3>
</lightning-card>
</template>
parentEx3.js
import { LightningElement} from 'lwc';
export default class ParentEx3 extends LightningElement {
passBday(event){
console.log(event.target.value);
this.template.querySelector('c-child-ex3').handleBday(event.target.value);
}
}
Example 4:
Let’s take another example where Parents will call a child component. In this example, the parent will call the child component method which will refresh the clock.
Child Component-
childEx4.html
<template>
<lightning-formatted-date-time
value={timestamp}
year="numeric"
month="numeric"
day="numeric"
hour="2-digit"
minute="2-digit"
second="2-digit"
time-zone-name="short"
>
</lightning-formatted-date-time>
</template>
childEx4.js
import { api, LightningElement } from 'lwc';
export default class ChildEx4 extends LightningElement {
timestamp = new Date();
@api
refresh(){
console.log('This is refresh');
this.timestamp=new Date();
}
}
Parent Component-
parentEx4.html
<template>
<lightning-card>
<lightning-button variant="brand"
label="Brand"
title="Refresh Talk"
onclick={handleClick}
class="slds-m-left_x-small"></lightning-button>
<c-child-ex4></c-child-ex4>
</lightning-card>
</template>
parentEx4.js
import { LightningElement } from 'lwc';
export default class ParentEx4 extends LightningElement {
handleClick(){
console.log('this is handleClick');
this.template.querySelector('c-child-ex4').refresh();
}
}
So points to note down for Parent to child communication is:
Child to Parent communication
Now let’s understand how the child LWC communicates to the parent LWC. In order to make a communication, create and dispatch the custom event from the child component and listen from the parent.
Create & Dispatch an Event on Child LWC
Create Event
We can use the customEvent() constructor to create an event. The CustomEvent() constructor has one required parameter, which is a string indicating the event type. As a component author, you name the event type when you create the event. You can use any string as your event type. However, Salesforce recommends that conform with the DOM event standard.
Syntax
new customEvent(eventName, props);
Note-Don’t prefix your event name with the string on, because inline event handler names must start with the string on. If your event is called onmessage, the markup would be <c-my-component ononmessage={handleMessage}>. Notice the doubled word onon, which is confusing.
Dispatch Event
We have to dispatch an event with EventTarget.dispatchEvent() method.
Syntax
this.dispatchEvent(new customEvent(eventName, props);
Handle an Event on Parent LWC
There are two ways to listen to an event
Declarative via html markup : We need to add an “on” prefix in the event name in Parent Component during calling of Child Component for Declaratively event listener.
ParentComponent
<template>
<c-child-component oneventName={listenerHandler}></c-child-component >
</template>
JavaScript using addEventListener method : We can explicitly attach a listener by using the addEventListner method in the parent component like below :
ChildComponent
this.template.addEventListener('eventName', this.handleNotification.bind(this));
Example
childEx4.html
The c-childEx4 component contains Previous and Next buttons. When a user clicks the buttons, the component creates and dispatches previous and next events. You can drop the paginator component into any component that needs Previous and Next buttons. That parent component listens for the events and handles them.
<template>
<lightning-layout>
<lightning-layout-item>
<lightning-button
label="Previous"
icon-name="utility:chevronleft"
onclick={handlePrevious}
></lightning-button>
</lightning-layout-item>
<lightning-layout-item flexibility="grow"></lightning-layout-item>
<lightning-layout-item>
<lightning-button
label="Next"
icon-name="utility:chevronright"
icon-position="right"
onclick={handleNext}
></lightning-button>
</lightning-layout-item>
</lightning-layout>
</template>
childEx4.js
When a user clicks a button, the previousHandler or nextHandler function executes. These functions create and dispatch the previous and next events.
import { LightningElement } from 'lwc';
export default class Paginator extends LightningElement {
handlePrevious() {
this.dispatchEvent(new CustomEvent('previous'));
}
handleNext() {
this.dispatchEvent(new CustomEvent('next'));
}
}
These events are simple “something happened” events. They don’t pass a data payload up the DOM tree, they simply announce that a user clicked a button.
Let’s drop childEx5 into a component called c-parent-ex5, which listens for and handles the previous and next events.
parentEx5.html
To listen for events, use an HTML attribute with the syntax oneventtype. Since our event types are previous and next, the listeners are onprevious and onnext.
<template>
<lightning-card title="Pagination">
Page {page}
<c-child-ex5 onprevious={previousHandler1}
onnext={nextHandler1}></c-child-ex5>
</lightning-card>
</template>
When c-parent-ex5 receives the previous and next events, previousHandler1 and nextHandler1 increase and decrease the page number. Here you notice, method name is not necessary to have the same name as child component.
import { LightningElement } from 'lwc';
export default class ParentEx5 extends LightningElement {
page = 1;
previousHandler1() {
console.log('This is previousHandler1 in parent component');
if (this.page > 1) {
this.page = this.page - 1;
}
}
nextHandler1() {
console.log('This is nextHandler1 in parent component');
this.page = this.page + 1;
}
}