Tuesday, 8 May 2018

Angular - Event binding (event)

The bindings directives you've met so far flow data in one direction: from a component to an element.
Users don't just stare at the screen. They enter text into input boxes. They pick items from lists. They click buttons. Such user actions may result in a flow of data in the opposite direction: from an element to a component.
The only way to know about a user action is to listen for certain events such as keystrokes, mouse movements, clicks, and touches. You declare your interest in user actions through Angular event binding.
Event binding syntax consists of a target event name within parentheses on the left of an equal sign, and a quoted template statement on the right. The following event binding listens for the button's click events, calling the component's onSave() method whenever a click occurs:
src/app/app.component.html
<button (click)="onSave()">Save</button>

Target event:
A name between parentheses — for example, (click) — identifies the target event. In the following example, the target is the button's click event.
src/app/app.component.html
<button (click)="onSave()">Save</button>
Some people prefer the on- prefix alternative, known as the canonical form:
src/app/app.component.html
<button on-click="onSave()">On Save</button>
Element events may be the more common targets, but Angular looks first to see if the name matches an event property of a known directive, as it does in the following example:
src/app/app.component.html
<!-- `myClick` is an event on the custom `ClickDirective` -->
<div (myClick)="clickMessage=$event" clickable>click with myClick</div>
If the name fails to match an element event or an output property of a known directive, Angular reports an “unknown directive” error.

$event and event handling statements:
In an event binding, Angular sets up an event handler for the target event.
When the event is raised, the handler executes the template statement. The template statement typically involves a receiver, which performs an action in response to the event, such as storing a value from the HTML control into a model.
The binding conveys information about the event, including data values, through an event object named $event.
The shape of the event object is determined by the target event. If the target event is a native DOM element event, then $event is a DOM event object, with properties such as target and target.value.
src/app/app.component.html
<input [value]="currentHero.name"
       (input)="currentHero.name=$event.target.value" >
This code sets the input box value property by binding to the name property. To listen for changes to the value, the code binds to the input box's input event. When the user makes changes, the input event is raised, and the binding executes the statement within a context that includes the DOM event object, $event.
To update the name property, the changed text is retrieved by following the path $event.target.value.
If the event belongs to a directive (recall that components are directives), $event has whatever shape the directive decides to produce.

Custom events with EventEmitter:
Directives typically raise custom events with an Angular EventEmitter. The directive creates an EventEmitter and exposes it as a property. The directive calls EventEmitter.emit(payload) to fire an event, passing in a message payload, which can be anything. Parent directives listen for the event by binding to this property and accessing the payload through the $event object.
Consider a HeroDetailComponent that presents hero information and responds to user actions. Although the HeroDetailComponent has a delete button it doesn't know how to delete the hero itself. The best it can do is raise an event reporting the user's delete request.
Here are the pertinent excerpts from that HeroDetailComponent:
src/app/hero-detail.component.ts (template)
template: `
<div>
  <img src="{{heroImageUrl}}">
  <span [style.text-decoration]="lineThrough">
    {{prefix}} {{hero?.name}}
  </span>
  <button (click)="delete()">Delete</button>
</div>`
src/app/hero-detail.component.ts (deleteRequest)
// This component makes a request but it can't actually delete a hero.
deleteRequest = new EventEmitter<Hero>();

delete() {
  this.deleteRequest.emit(this.hero);
}
The component defines a deleteRequest property that returns an EventEmitter. When the user clicks delete, the component invokes the delete() method, telling the EventEmitter to emit a Hero object.
Now imagine a hosting parent component that binds to the HeroDetailComponent's deleteRequest event.
src/app/app.component.html (event-binding-to-component)
<app-hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></app-hero-detail>
When the deleteRequest event fires, Angular calls the parent component's deleteHero method, passing the hero-to-delete (emitted by HeroDetail) in the $event variable.

Two-way binding ( [(...)] )
You often want to both display a data property and update that property when the user makes changes.
On the element side that takes a combination of setting a specific element property and listening for an element change event.
Angular offers a special two-way data binding syntax for this purpose, [(x)]. The [(x)] syntax combines the brackets of property binding[x], with the parentheses of event binding(x).
[( )] = BANANA IN A BOX, Visualize a banana in a box to remember that the parentheses go inside the brackets.
The [(x)] syntax is easy to demonstrate when the element has a settable property called x and a corresponding event named xChange. Here's a SizerComponent that fits the pattern. It has a size value property and a companion sizeChange event:
src/app/sizer.component.ts
import { Component, EventEmitter, Input, Output } from '@angular/core';

  selector: 'app-sizer',
  template: `
  <div>
    <button (click)="dec()" title="smaller">-</button>
    <button (click)="inc()" title="bigger">+</button>
    <label [style.font-size.px]="size">FontSize: {{size}}px</label>
  </div>`
})
export class SizerComponent {
  @Input()  size: number | string;
  @Output() sizeChange = new EventEmitter<number>();

  dec() { this.resize(-1); }
  inc() { this.resize(+1); }

  resize(delta: number) {
    this.size = Math.min(40, Math.max(8, +this.size + delta));
    this.sizeChange.emit(this.size);
  }
}
The initial size is an input value from a property binding. Clicking the buttons increases or decreases the size, within min/max values constraints, and then raises (emits) the sizeChange event with the adjusted size.
Here's an example in which the AppComponent.fontSizePx is two-way bound to the SizerComponent:
src/app/app.component.html (two-way-1)
<app-sizer [(size)]="fontSizePx"></app-sizer>
<div [style.font-size.px]="fontSizePx">Resizable Text</div>
The AppComponent.fontSizePx establishes the initial SizerComponent.size value. Clicking the buttons updates the AppComponent.fontSizePx via the two-way binding. The revised AppComponent.fontSizePx value flows through to the style binding, making the displayed text bigger or smaller.
The two-way binding syntax is really just syntactic sugar for a property binding and an event binding. Angular desugars the SizerComponent binding into this:
src/app/app.component.html (two-way-2)
<app-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-sizer>
The $event variable contains the payload of the SizerComponent.sizeChange event. Angular assigns the $event value to the AppComponent.fontSizePx when the user clicks the buttons.
Clearly the two-way binding syntax is a great convenience compared to separate property and event bindings.
It would be convenient to use two-way binding with HTML form elements like <input> and <select>. However, no native HTML element follows the x value and xChange event pattern.
Fortunately, the Angular NgModel directive is a bridge that enables two-way binding to form elements.



1 comment:

  1. I have read your blog its very attractive and impressive. I like it your blog.
    appvn app

    ReplyDelete