Angular 2 will often provide you with an Observable interface to attach to for free, but it is important to know how they are created, configured, and used. More specifically, it is valuable for you to know how to take Observables and apply them to real scenarios that will be encountered in the client.
The code, links, and a live example of this are available at .
Suppose you started with the following skeleton application:
[app/click-observer.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'click-observer', template: ` <button> Emit event! </button> <p *ngFor="let click of clicks; let i = index"> {{i}}: {{click}} </p> ` }) export class ClickObserverComponent { clicks:Array<Event> = []; } Your goal is to convert this so that all the button click events are logged in to the repeated field.
Accomplishing this with a component member method and using it in the click event binding in the template is possible, but this doesn't capture the real value of Observables. You want to be able to expose an Observable on ClickObserverComponent. This will allow any other part of your application to subscribe to these click events and handle them in its own way.
Instead, you would like to be able to funnel the click events from the button into the Observable. With a regular Observable instance, this isn't possible since it is only acting as the Subscribe part of the Publish-Subscribe model. To accomplish the Publish aspect, you must use a Subject instance:
[app/click-observer.component.ts] import {Component} from '@angular/core'; import {Subject} from 'rxjs/Subject'; @Component({ selector: 'click-observer', template: ` <button> Emit event! </button> <p *ngFor="let click of clicks; let i = index"> {{i}}: {{click}} </p> ` }) export class ClickObserverComponent { clickEmitter:Subject<Event> = new Subject(); clicks:Array<Event> = []; } ReactiveX Subjects act as both the Observable and the Observer. Therefore, it exposes both the subscribe() method, used for the Subscribe behavior, and the next() method, used for the Publish behavior.
In this example, the next() method is useful because you want to explicitly specify when an emission should occur and what that emission should contain. There are lots of ways of instantiating Observables in order to implicitly generate emissions, such as (but certainly not limited to) Observable.range(). In these cases, Observable understands how its input behaves, and thus it does not need direction as to when emissions occur and what they should contain.
In this case, you can pass the event directly to next() in the template click handler definition. With this, all that is left is to populate the array by directing the emissions into it:
[app/click-observer.component.ts] import {Component} from '@angular/core'; import {Subject} from 'rxjs/Subject'; @Component({ selector: 'click-observer', template: ` <button (click)="clickEmitter.next($event)"> Emit event! </button> <p *ngFor="let click of clicks; let i = index"> {{i}}: {{click}} </p> ` }) export class ClickObserverComponent { clickEmitter:Subject<Event> = new Subject(); clicks:Array<Event> = []; constructor() { this.clickEmitter .subscribe(clickEvent => this.clicks.push(clickEvent)); } } That's all! With this, you should see click events populate in the browser with each successive button click.
ReactiveX Observables and Observers are distinct, but their behavior is mutually compatible in such a way that their union, Subject, can act as either one of them. In this example, the Subject is used as the interface to feed in Event objects as the Publish modality as well as to handle the result that would come out as the Subscribe modality.
The way this is constructed might feel a bit strange to you. The component is exposing the Subject instance as the point where your application will attach observer handlers.
However, you want to prevent other parts of the application from adding additional events, which is still possible should they choose to use the next() method. What's more, the Subject instance is referenced directly inside the template and exposing it there may feel a bit odd. Therefore, it is desirable, and certainly good software practice, to only expose the Observable component of the Subject.
To do this, you must import the Observable module and utilize the Subject instance's member method, namely asObservable(). This method will create a new Observable instance that will effectively pipe the observed emissions from the Subject into the new Observable, which will be exposed as a public component member:
[app/article.component.ts] import {Component} from '@angular/core'; import {Observable} from 'rxjs/Observable'; import {Subject} from 'rxjs/Subject'; @Component({ selector: 'click-observer', template: ` <button (click)="publish($event)"> Emit event! </button> <p *ngFor="let click of clicks; let i = index"> {{i}}: {{click}} </p> ` }) export class ClickObserverComponent { clickEmitter: Observable<Event>; private clickSubject_: Subject<Event> = new Subject(); clicks:Array<Event> = []; constructor() { this.clickEmitter = this.clickSubject_.asObservable(); this.clickEmitter.subscribe(clickEvent => this.clicks.push(clickEvent)); } publish(e:Event):void { this.clickSubject_.next(e); } } Now even though only this component is referencing clickEmitter, every component that uses clickEmitter will not need or be able to touch the source, Subject.
This has all been a great example, but this is such a common pattern in that the RxJS library already provides a built-in way of implementing it. The Observable class exposes a static method fromEvent(), which takes in an element that is expected to generate events and the event type to listen to.
However, you need a reference to the actual element, which you currently do not have. For the present implementation, the Angular 2 ViewChild faculties will give you a very nice reference to the button, which will then be passed to the fromEvent() method once the template has been rendered:
[app/click-observer.component.ts] import {Component, ViewChild, ngAfterViewInit} from '@angular/core'; import {Observable} from 'rxjs/Observable'; import 'rxjs/add/observable/fromEvent'; @Component({ selector: 'click-observer', template: ` <button #btn> Emit event! </button> <p *ngFor="let click of clicks; let i = index"> {{i}}: {{click}} </p> ` }) export class ClickObserverComponent implements AfterViewInit { @ViewChild('btn') btn; clickEmitter:Observable<Event>; clicks:Array<Event> = []; ngAfterViewInit() { this.clickEmitter = Observable.fromEvent( this.btn.nativeElement, 'click'); this.clickEmitter.subscribe(clickEvent => this.clicks.push(clickEvent)); } } With all of this, the component should still behave identically.