Data Binding Between Components

When you add a component in markup, you can use an expression to initialize attribute values in the component based on attribute values of the container component. There are two forms of expression syntax, which exhibit different behaviors for data binding between the components.
This concept is a little tricky, but it will make more sense when we look at an example. Consider a c:parent component that has a parentAttr attribute. c:parent contains a c:child component with a childAttr attribute that’s initialized to the value of the parentAttr attribute. We’re passing the parentAttr attribute value from c:parent into the c:child component, which results in a data binding, also known as a value binding, between the two components.
1<!--c:parent-->
2<aura:component>
3    <aura:attribute name="parentAttr" type="String" default="parent attribute"/>
4     
5    <!-- Instantiate the child component -->
6    <c:child childAttr="{!v.parentAttr}" />
7</aura:component>
{!v.parentAttr} is a bound expression. Any change to the value of the childAttr attribute in c:child also affects the parentAttr attribute in c:parent and vice versa.
Now, let's change the markup from:
1<c:child childAttr="{!v.parentAttr}" />
to:
1<c:child childAttr="{#v.parentAttr}" />
{#v.parentAttr} is an unbound expression. Any change to the value of the childAttr attribute in c:child doesn’t affect the parentAttr attribute in c:parent and vice versa.
Here’s a summary of the differences between the forms of expression syntax.
{#expression} (Unbound Expressions)
Data updates behave as you would expect in JavaScript. Primitives, such as String, are passed by value, and data updates for the expression in the parent and child are decoupled.
Objects, such as Array or Map, are passed by reference, so changes to the data in the child propagate to the parent. However, change handlers in the parent aren’t notified. The same behavior applies for changes in the parent propagating to the child.
{!expression} (Bound Expressions)
Data updates in either component are reflected through bidirectional data binding in both components. Similarly, change handlers are triggered in both the parent and child components.
Tip
Bi-directional data binding is expensive for performance and it can create hard-to-debug errors due to the propagation of data changes through nested components. We recommend using the {#expression}syntax instead when you pass an expression from a parent component to a child component unless you require bi-directional data binding.

Unbound Expressions

Let’s look at another example of a c:parentExpr component that contains another component, c:childExpr.
Here is the markup for c:childExpr.
1<!--c:childExpr-->
2<aura:component>
3    <aura:attribute name="childAttr" type="String" />
4 
5    <p>childExpr childAttr: {!v.childAttr}</p>
6    <p><lightning:button label="Update childAttr"
7          onclick="{!c.updateChildAttr}"/></p>
8</aura:component>
Here is the markup for c:parentExpr.
01<!--c:parentExpr-->
02<aura:component>
03    <aura:attribute name="parentAttr" type="String" default="parent attribute"/>
04 
05    <!-- Instantiate the child component -->
06    <c:childExpr childAttr="{#v.parentAttr}" />
07        
08    <p>parentExpr parentAttr: {!v.parentAttr}</p>
09    <p><lightning:button label="Update parentAttr"
10          onclick="{!c.updateParentAttr}"/></p>
11</aura:component>
The c:parentExpr component uses an unbound expression to set an attribute in the c:childExpr component.
1<c:childExpr childAttr="{#v.parentAttr}" />
When we instantiate childExpr, we set the childAttr attribute to the value of the parentAttr attribute in c:parentExpr. Since the {#v.parentAttr} syntax is used, the v.parentAttr expression is not bound to the value of the childAttr attribute.
The c:exprApp application is a wrapper around c:parentExpr.
1<!--c:exprApp-->
2<aura:application >
3    <c:parentExpr />
4</aura:application>
In the Developer Console, click Preview in the sidebar for c:exprApp to view the app in your browser.
Both parentAttr and childAttr are set to “parent attribute”, which is the default value of parentAttr.
Now, let’s create a client-side controller for c:childExpr so that we can dynamically update the component. Here is the source for childExprController.js.
1/* childExprController.js */
2({
3    updateChildAttr: function(cmp) {
4        cmp.set("v.childAttr""updated child attribute");
5    }
6})
Press the Update childAttr button. This updates childAttr to “updated child attribute”. The value of parentAttr is unchanged since we used an unbound expression.
1<c:childExpr childAttr="{#v.parentAttr}" />
Let’s add a client-side controller for c:parentExpr. Here is the source for parentExprController.js.
1/* parentExprController.js */
2({
3    updateParentAttr: function(cmp) {
4        cmp.set("v.parentAttr""updated parent attribute");
5    }
6})
In the Developer Console, click Update Preview for c:exprApp.
Press the Update parentAttr button. This time, parentAttr is set to “updated parent attribute” while childAttr is unchanged due to the unbound expression.
Warning
Don’t use a component’s init event and client-side controller to initialize an attribute that is used in an unbound expression. The attribute will not be initialized. Use a bound expression instead. For more information on a component’s init event, see Invoking Actions on Component Initialization.
Alternatively, you can wrap the component in another component. When you instantiate the wrapped component in the wrapper component, initialize the attribute value instead of initializing the attribute in the wrapped component’s client-side controller.

Bound Expressions

Now, let’s update the code to use a bound expression instead. Change this line in c:parentExpr:
1<c:childExpr childAttr="{#v.parentAttr}" />
to:
1<c:childExpr childAttr="{!v.parentAttr}" />
In the Developer Console, click Update Preview for c:exprApp.
Press the Update childAttr button. This updates both childAttr and parentAttr to “updated child attribute” even though we only set v.childAttr in the client-side controller of childExpr. Both attributes were updated since we used a bound expression to set the childAttr attribute.

Change Handlers and Data Binding

You can configure a component to automatically invoke a change handler, which is a client-side controller action, when a value in one of the component's attributes changes.
When you use a bound expression, a change in the attribute in the parent or child component triggers the change handler in both components. When you use an unbound expression, the change is not propagated between components so the change handler is only triggered in the component that contains the changed attribute.
Let’s add change handlers to our earlier example to see how they are affected by bound versus unbound expressions.
Here is the updated markup for c:childExpr.
01<!--c:childExpr-->
02<aura:component>
03    <aura:attribute name="childAttr" type="String" />
04 
05    <aura:handler name="change" value="{!v.childAttr}" action="{!c.onChildAttrChange}"/>
06 
07    <p>childExpr childAttr: {!v.childAttr}</p>
08    <p><lightning:button label="Update childAttr"
09          onclick="{!c.updateChildAttr}"/></p>
10</aura:component>
Notice the <aura:handler> tag with name="change", which signifies a change handler. value="{!v.childAttr}" tells the change handler to track the childAttr attribute. When childAttr changes, the onChildAttrChange client-side controller action is invoked.
Here is the client-side controller for c:childExpr.
01/* childExprController.js */
02({
03    updateChildAttr: function(cmp) {
04        cmp.set("v.childAttr""updated child attribute");
05    },
06 
07    onChildAttrChange: function(cmp, evt) {
08        console.log("childAttr has changed");
09        console.log("old value: " + evt.getParam("oldValue"));
10        console.log("current value: " + evt.getParam("value"));
11    }
12})
Here is the updated markup for c:parentExpr with a change handler.
01<!--c:parentExpr-->
02<aura:component>
03    <aura:attribute name="parentAttr" type="String" default="parent attribute"/>
04 
05    <aura:handler name="change" value="{!v.parentAttr}" action="{!c.onParentAttrChange}"/>
06 
07    <!-- Instantiate the child component -->
08    <c:childExpr childAttr="{!v.parentAttr}" />
09        
10    <p>parentExpr parentAttr: {!v.parentAttr}</p>
11    <p><lightning:button label="Update parentAttr"
12          onclick="{!c.updateParentAttr}"/></p>
13</aura:component>
Here is the client-side controller for c:parentExpr.
01/* parentExprController.js */
02({
03    updateParentAttr: function(cmp) {
04        cmp.set("v.parentAttr""updated parent attribute");
05    },
06 
07    onParentAttrChange: function(cmp, evt) {
08        console.log("parentAttr has changed");
09        console.log("old value: " + evt.getParam("oldValue"));ui
10        console.log("current value: " + evt.getParam("value"));
11    }
12})
In the Developer Console, click Update Preview for c:exprApp.
Open your browser’s console (More tools | Developer tools in Chrome).
Press the Update parentAttr button. The change handlers for c:parentExpr and c:childExpr are both triggered as we’re using a bound expression.
1<c:childExpr childAttr="{!v.parentAttr}" />
Change c:parentExpr to use an unbound expression instead.
1<c:childExpr childAttr="{#v.parentAttr}" />
In the Developer Console, click Update Preview for c:exprApp.
Press the Update childAttr button. This time, only the change handler for c:childExpr is triggered as we’re using an unbound expression.

No comments:

Counters