Книга: Angular 2 Cookbook
Назад: Basic utilization of Observables with HTTP
Дальше: Creating an Observable authentication service using BehaviorSubjects

Implementing a Publish-Subscribe model using Subjects

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.

Note

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

Getting ready

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.

How to do it...

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.

Note

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.

How it works...

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.

There's more...

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.

Native RxJS implementation

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.

See also

  • Basic utilization of Observables with HTTP demonstrates the basics of how to use an observable interface
  • Creating an Observable authentication service using BehaviorSubjects instructs you on how to reactively manage the state in your application
  • Building a fully featured AutoComplete with Observables gives you a broad tour of some of the utilities offered to you as part of the RxJS library
Назад: Basic utilization of Observables with HTTP
Дальше: Creating an Observable authentication service using BehaviorSubjects

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