Книга: Angular 2 Cookbook
Назад: Registering handlers on native browser events
Дальше: Attaching behavior to DOM elements with directives

Generating and capturing custom events using EventEmitter

In the wake of the disappearance of $scope, Angular was left with a void for propagating information up the component tree. This void is filled in part by custom events, and they represent the Yin to the downward data binding Yang.

Note

The code, links, and a live example of this are available at .

Getting ready

Suppose you had an Article application as follows:

[app/text-editor.component.ts]      import {Component} from '@angular/core';      @Component({     selector: 'text-editor',     template: `       <textarea></textarea>     `   })   export class TextEditorComponent {}      [app/article.component.ts]      import {Component} from '@angular/core';      @Component({     selector: 'article',     template: `       <h1>{{title}}</h1>       <p>Word count: {{wordCount}}</p>       <text-editor></text-editor>     `   })   export class ArticleComponent {      title:string = `       Maternity Ward Resorts to Rock Paper Scissors Following        Baby Mixup`;     wordCount:number = 0;        updateWordCount(e:number):void {       this.wordCount = e;     }   }   

This application will ideally be able to read the content of textarea when there is a change, and also count the number of words and report it to the parent component to be interpolated. As is the case, none of this is implemented.

How to do it...

A developer thinking in terms of Angular 1 would attach ng-model to textarea, use $scope.$watch on the model data, and pass the data to the parent via $scope or some other means. Unfortunately for such a developer, these constructs are radically different or non-existent in Angular 2. Fear not! The new implementation is more expressive, more modular, and much cleaner.

Capturing the event data

ngModel still exists in Angular 2, and it would certainly be suitable here. However, you don't actually need to use ngModel at all, and in this case, it allows you to be more explicit about when your application takes action. First, you must retrieve the text from the textarea element and make it usable in TextEditorComponent:

[app/text-editor.component.ts]      import {Component} from '@angular/core';      @Component({     selector: 'text-editor',     template: `       <textarea (keyup)="emitWordCount($event)"></textarea>     `   })   export class TextEditorComponent {       emitWordCount(e:Event):void {       console.log(         (e.target.value.match(/\S+/g) || []).length);     }   }    

Excellent! As claimed, you don't need to use ngModel to acquire the element's contents. What's more, you are now able to utilize native browser events to explicitly define when you want TextEditorComponent to take action.

With this, you are setting a listener on the native browser's keyup event, fired from the textarea element. This event has a target property that exposes the value of the text in the element, which is exactly what you want to use. The component then uses a simple regular expression to count the number of non-whitespace sequences. This is your word count.

Emitting a custom event

console.log does not help to inform the parent component of the word count you are calculating. To do this, you need to create a custom event and emit it upwards:

[app/text-editor.component.ts]      import {Component, EventEmitter, Output} from '@angular/core';      @Component({     selector: 'text-editor',     template: `       <textarea (keyup)="emitWordCount($event)"></textarea>     `   })   export class TextEditorComponent {       @Output() countUpdate = new EventEmitter<number>();        emitWordCount(e:Event):void {       this.countUpdate.emit(         (e.target.value.match(/\S+/g) || []).length);     }   }   

Using the @Output decorator allows you to instantiate an EventEmitter member on the child component that the parent component will be able to listen to. This EventEmitter member, like any other class member, is available as this.countUpdate. The child component is able to send events upward by invoking the emit() method on this member, and the argument to this method is the value which you wish to send to the event. Here, since you want to send an integer count of words, you instantiate the EventEmitter member by typing it as a <number> emitter.

Listening for custom events

So far, you are through with only half the implementation, as these custom events are being fired off into the ether of the browser with no listeners. Since the method you need to use is already defined on the parent component, all you need to do is hook into the event listener to that method.

The ( ) template syntax is used to add listeners to events, and Angular does not discriminate between native browser events and events that originate from EventEmitters. Thus, since you declared the child component's EventEmitter as @Output, you will be able to add a listener for events that come from it on the parent component, as follows:

[app/article.component.ts]      import {Component } from 'angular2/core';      @Component({     selector: 'article',     template: `       <h1>{{title}}</h1>       <p>Word count: {{wordCount}}</p>       <text-editor (countUpdate)="updateWordCount($event)">       </text-editor>     `   })   export class ArticleComponent {      title:string = `       Maternity Ward Resorts to Rock Paper Scissors Following        Baby Mixup`;     wordCount:number = 0;        updateWordCount(e:number):void {       this.wordCount = e;     }   }   

With this, your application should correctly count the words in the TextEditor component and update the value in the Article component.

How it works...

Using @Output in conjunction with EventEmitter allows you to create child components that expose an API for the parent component to hook into. The EventEmitter sends the events upward with its emit method, and the parent component can subscribe to them by binding to the emitter output.

The flow of this example is as follows:

  1. The keystroke inside textarea causes the native browser's keyup event.
  2. The TextEditor component has a listener set on this event, so the attached expression is evaluated, which will invoke emitWordCount.
  3. The emitWordCount inspects the Event object and extracts the text from the associated DOM element. It parses the text for the number of contained words and invokes the EventEmitter.emit method.
  4. The EventEmitter method emits an event associated with the declared countUpdate @Output member.
  5. The ArticleComponent sees this event and invokes the attached expression. The expression invokes updateWordCount, passing in the event value.
  6. The ArticleComponent property is updated, and since this value is interpolated in the view, Angular honors the data binding process by updating the view.

There's more...

The name EventEmitter is a bit deceiving. If you're paying attention, you will notice that the parent component member method invoked in the handler does not have a typed parameter. You will also notice that you are directly assigning that parameter to the member typed as number. This should seem odd as the template expression invoking the method is passing $event, which you used earlier as a browser Event object. This seems like a mismatch because it is a mismatch. If you bind to native browser events, the event you will observe can only be the native browser event object. If you bind to custom events, the event you will observe is whatever was passed when emit was invoked. Here, the parameter to updateWordCount() is simply the integer you provided with this.countUpdate.emit().

Also note that you are not required to provide a value for the emitted event. You can still use EventEmitter to signal to a parent component that an event has occurred and that it should evaluate the bound expression. To do this, you simply create an untyped emitter with new EventEmitter() and invoke emit() with no arguments. $event should be undefined.

It is not possible to pass multiple values as custom events. To send multiple pieces of data, you need to combine them into an object or array.

See also

  • Binding to native element attributes shows how Angular 2 interfaces with HTML element attributes.
  • Registering handlers on native browser events demonstrates how you can easily attach behavior to browser events.
Назад: Registering handlers on native browser events
Дальше: Attaching behavior to DOM elements with directives

thank you
Flame
cant read the code since it is all on a single line. Also this comments section is russian
Rakuneque
DATA COLLECTION AND ANALYSIS Two reviewers extracted data and assessed methodological quality independently lasix torsemide conversion Many others were in that space already