Serhii Polishchuk profile picture Serhii Polishchuk

Angular Data Binding

Feb 07, 2018

Class binding

<!--funny-button.component.html-->
<button (click)="toggle()" [class.great]="isGreat" [class.isnt-great]="!isGreat">Is it Great?</button>
/*funny-button.component.css*/
.great {
    background-color: red;
}
.isnt-great {
    background-color: green;
}
// funny-button.component.ts
  public isGreat: boolean;
  public toggle() {
    this.isGreat = !this.isGreat;
  }

Style binding

<!-- funny-button.component.html -->
<button (click)="toggle()" [style.background-color]="color">Is it Great?</button>
// funny-button.component.ts
  public color = 'red';
  toggle() {
      this.color = this.color === 'green' ? 'red' : 'green';
  }

Button will toggle ‘background-color’ property after each click on button

Event binding

This example log the current (before change) background color of the button

<!--funny-button.component.html-->
<button (click)="toggle($event)" [style.background-color]="color">Is it Great?</button>
// funny-button.component.ts
    public color = 'red';
    toggle($event: MouseEvent) {
        console.dir($event.target.style.backgroundColor);
        this.color = this.color === 'green' ? 'red' : 'green';
    }

$event argument, has a special meaning, could be either DOM or Angular event - depends on event name. $event argument can be used in template expression:

<!--funny-button.component.html-->
<input [value]="color" (input)="color = $event.target.value">
<button [style.background-color]="color">Is it Great?</button>
      public color = 'red';

Put in input whatever color you like, it immediately change background color of button. Easy, isn’t it?

Custom Events

This is common practice in any backend framework, like Symfony, to emit a custom events. In Angular there is an EventEmitter for that purposes. It’s plays well with parent-child components that stay on the same page. You can emit event on one component and subscribe on it in another.

So get create another one component:

ng g component funny-button-child

Now we can add some template:

<!--funny-button-child.component.html-->
<input (input)="obj.text = $event.target.value; emitCustomEvent()">

And implement emitting event in child component

// funny-button-child.component.ts
export class FunnyButtonChildComponent {
  public obj = {};
  @Output() myEvent: EventEmitter<any> = new EventEmitter();

  emitCustomEvent() {
    this.myEvent.emit(this.obj);
  }
}

In parent component now we can catch the myEvent event:

<!--funny-button.component.html-->
<app-funny-button-child (myEvent)="checkEvent($event)"></app-funny-button-child>

And let just log our $event (there is object with text property):

// funny-button.component.ts
export class FunnyButtonComponent {
    checkEvent($event) {
        console.log($event);
    }
}

More in EventEmitter documentation https://angular.io/api/core/EventEmitter

Two way binging

The simplest way to get two way data binding in Angular is “banana in the box” [()] Lets make simple example:

// funny-button.component.ts
import {Component} from '@angular/core';

@Component({
  selector: 'app-funny-button',
  template: `
    <label>{{data}}</label>
    <input [(ngModel)]="data">
  `
})
export class FunnyButtonComponent {
    public data: string;
}

Label will appear while you input text

Complex example

Here is a detection changes in child component

// child.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
    <label for="data">Child component</label>
    <input id="data" [(ngModel)]="data">
  `
})
export class ChildComponent {

  public data = 'Data Template';
}
// parent.component.ts
import {ChangeDetectorRef, Component, OnInit, ViewChild} from '@angular/core';
import {ChildComponent} from '../child/child.component';
import {AfterViewChecked, AfterViewInit} from '@angular/core/src/metadata/lifecycle_hooks';

@Component({
  selector: 'app-parent',
  template: `
    <p>Parent component</p>
    <app-child></app-child>
    <p [hidden]="!error.throw" [ngStyle]="{color: error.color}">{{error.msg}}</p>
  `,
  styles: []
})
export class ParentComponent implements AfterViewInit, AfterViewChecked, OnInit {

  @ViewChild(ChildComponent) child: ChildComponent;

  public error: { msg: string, color: string, throw: boolean };
  readonly maxInputLength = 25;

  constructor(private changeDetector: ChangeDetectorRef) { }

  ngOnInit(): void {
    this.error = {
      msg: 'The value is too loooooong!',
      color: 'red',
      throw: false
    };
  }

  ngAfterViewChecked(): void {
    this.error.throw = this.child.data.length > this.maxInputLength;
    this.changeDetector.detectChanges(); // prevent ExpressionChangedAfterItHasBeenCheckedError
  }

  ngAfterViewInit(): void {
    console.log('ngAfterViewInit');
  }
}