Angular — Custom two way data binding

Unlike AngularJS, Angular does not provide two way binding by default, which avoids all the digest cycle and watchers issues that AngularJS dealt with. But sometimes, there would be a need for a two way data binding between two components — Parent and its child. This article demonstrates how to create two way data binding between two components.

Before diving into implementing custom two way binding, we should understand the binding concepts below:
* Property Binding
* Event Binding

Property Binding

When you pass the data property of a component as an attribute within the square brackets [] to the target element in html, it is called Property Binding.

//component-a.template.html
<component-b [amount]=”amount”></component-b>

The square bracket tells the angular to evaluate the expression given in the right hand side and it is a live property, which means when Component A updates the property amount , Component B will receive the updates. The communication is one-way though. When Component B updates the property amount , Component A would not know about it, which is why we need something called Event binding.

Event Binding

A component can listen to an event raised by another component by adding the target event within parenthesis () in the target element. It is called Event Binding.

//component-a.template.html
<component-b (amountChange)=”doSomething()”> </component-b>

Now combining these both, we could create a two-way binding property in components. The syntax would be [()], which is popularly called banana-in-a-box.


Let’s dig into this with an example. Let’s create a custom ParentComponent with a property amount in it and provide a button to the user to deposit more amount. Clicking the button would increase the amount value.

@Component({
selector: ‘parent’,
template: `
Parent Component:
Available amount: {{amount}}
<button (click)=”deposit()”>Deposit 100</button>
`,
})
export class ParentComponent {
amount: number = 500;
constructor() {
}

deposit(){
this.amount +=100;
}
}

Next, let’s add a custom ChildComponent and pass the property amount from child to parent. Remember Property Binding? Yes, we are going to add a property binding between parent and child component using @Input()decorator.

@Component({
selector: ‘child’,
template: `
<div>
Child component:
<span>Amount available: {{amount}}</span>
</div>
`,
})
export class ChildComponent{
@Input() amount: number;
}

Let’s update the ParentComponent template to render ChildComponent by using its selector and pass the amount property to ChildComponent .

Parent Component:
Available amount: {{amount}}
<button (click)=”deposit()”>Deposit 100</button>
<child [amount]=”amount”> </child>

Thats it, Property binding is done. When ParentComponent updates the amount, ChildComponent would know it.

Next, let’s add a method in ChildComponent to withdraw the money from the balance and provide the user a button to withdraw money.

@Component({
selector: ‘child’,
template: `
<div>
Child component:
<span>Amount available: {{amount}}</span>
<button (click)=”withdraw()”>Withdraw 100 </button>
</div>
`,
})
export class ChildComponent{
@Input() amount: number;

withdraw(){
this.amount -= 100;
}

}

Now the ChildComponent could withdraw money but ParentComponent would not know about it. How do we fix it? Event Binding to the rescue! Let’s make ChildComponent emit an event when it updates the amount property using @Output() decorator.

export class ChildComponent{
@Input() amount: number;
@Output() amountChange = new EventEmitter();

withdraw(){
this.amount -= 100;
this.amountChange.emit(this.amount);
}
}

Now make ParentComponent listen to the amountChange event and update its amount property

Parent Component:
Available amount: {{amount}}
<button (click)=”deposit()”>Deposit 100</button>
<child [amount]=”amount” (amountChange)="this.amount= $event.target.value"> </child>

Now when ChildComponent updates amount property , ParentComponent would also have the updated value.

Tadaa… Two-way binding done!

Banana in a box

Lets modify the syntax a bit in Parent’s template like below:

<child [(amount)]="amount"> </child>

The magic here is the suffix ‘Change’ in the event’s name amountChange. If the property binding name is ‘x’, the event binding name should be exactly ‘xChange’. Only then angular recognizes banana-in-a-box [()] syntax.

A downside of [()]

When we want to do something extra in theChildComponent when amount property gets updated , we could do the extra processing in angular life-cycle hook called ngOnChanges.

But we could not do the same in ParentComponent because ngOnChanges is triggered only when @Input() is changed (parent to child). In that case, we would want to split the banana-in-a-box back to the property and event binding syntax and do the extra processing in ParentComponent's event listener method.

Plunker to the above example: http://embed.plnkr.co/Jcu0iNkaUeRF7KEF7wA4/

Add claps if you find this article useful.