代表团:Angular2中的EventEmitter或Observable

我正在尝试在Angular2中实现类似于委托模式的东西。 当用户点击一个nav-item ,我想调用一个函数,然后发出一个事件,这个事件又应该由其他组件监听事件。

这里是场景:我有一个Navigation组件:

 import {Component, Output, EventEmitter} from 'angular2/core'; @Component({ // other properties left out for brevity events : ['navchange'], template:` <div class="nav-item" (click)="selectedNavItem(1)"></div> ` }) export class Navigation { @Output() navchange: EventEmitter<number> = new EventEmitter(); selectedNavItem(item: number) { console.log('selected nav item ' + item); this.navchange.emit(item) } } 

这里是观察部分:

 export class ObservingComponent { // How do I observe the event ? // <----------Observe/Register Event ?--------> public selectedNavItem(item: number) { console.log('item index changed!'); } } 

关键的问题是,我如何让观测部分观察相关事件?

更新2016-06-27:而不是使用Observables,使用任一

  • 行为主题,如@Abdulrahman在评论中推荐的,或者
  • 一个ReplaySubject,由@Jason Goemaat在评论中推荐

一个Subject是一个Observable(所以我们可以subscribe()它)和一个Observer(所以我们可以调用next()来发出一个新的值)。 我们利用这个function。 主题允许值被多播到许多观察者。 我们不利用这个function(我们只有一个观察者)。

BehaviorSubject是Subject的变体。 它有“当前价值”的概念。 我们利用这个:当我们创build一个ObservingComponent时,它会自动从BehaviorSubject中获取当前的导航项值。

下面的代码和plunker使用BehaviorSubject。

ReplaySubject是Subject的另一个变体。 如果要等到实际产生值,请使用ReplaySubject(1) 。 而行为主题需要初始值(将立即提供),而ReplaySubject不会。 ReplaySubject将始终提供最近的值,但由于它没有所需的初始值,所以在返回第一个值之前,服务可以执行一些asynchronous操作。 在最近的通话中,它仍然会立即启动。 如果您只需要一个值,请在订阅上使用first() 。 如果您使用first()则不必取消订阅。

 import {Injectable} from '@angular/core' import {BehaviorSubject} from 'rxjs/BehaviorSubject'; @Injectable() export class NavService { // Observable navItem source private _navItemSource = new BehaviorSubject<number>(0); // Observable navItem stream navItem$ = this._navItemSource.asObservable(); // service command changeNav(number) { this._navItemSource.next(number); } } 
 import {Component} from '@angular/core'; import {NavService} from './nav.service'; import {Subscription} from 'rxjs/Subscription'; @Component({ selector: 'obs-comp', template: `obs component, item: {{item}}` }) export class ObservingComponent { item: number; subscription:Subscription; constructor(private _navService:NavService) {} ngOnInit() { this.subscription = this._navService.navItem$ .subscribe(item => this.item = item) } ngOnDestroy() { // prevent memory leak when component is destroyed this.subscription.unsubscribe(); } } 
 @Component({ selector: 'my-nav', template:` <div class="nav-item" (click)="selectedNavItem(1)">nav 1 (click me)</div> <div class="nav-item" (click)="selectedNavItem(2)">nav 2 (click me)</div>` }) export class Navigation { item = 1; constructor(private _navService:NavService) {} selectedNavItem(item: number) { console.log('selected nav item ' + item); this._navService.changeNav(item); } } 

Plunker


使用Observable的原始答案:(它需要比使用BehaviorSubject更多的代码和逻辑,所以我不推荐它,但它可能是有启发性的)

所以,这是一个使用Observable 而不是EventEmitter的实现 。 与我的EventEmitter实现不同,这个实现也将当前select的navItem存储在服务中,这样当创build观察组件时,可以通过API调用navItem()来获取当前值,然后通过navChange$ Observable 。

 import {Observable} from 'rxjs/Observable'; import 'rxjs/add/operator/share'; import {Observer} from 'rxjs/Observer'; export class NavService { private _navItem = 0; navChange$: Observable<number>; private _observer: Observer; constructor() { this.navChange$ = new Observable(observer => this._observer = observer).share(); // share() allows multiple subscribers } changeNav(number) { this._navItem = number; this._observer.next(number); } navItem() { return this._navItem; } } @Component({ selector: 'obs-comp', template: `obs component, item: {{item}}` }) export class ObservingComponent { item: number; subscription: any; constructor(private _navService:NavService) {} ngOnInit() { this.item = this._navService.navItem(); this.subscription = this._navService.navChange$.subscribe( item => this.selectedNavItem(item)); } selectedNavItem(item: number) { this.item = item; } ngOnDestroy() { this.subscription.unsubscribe(); } } @Component({ selector: 'my-nav', template:` <div class="nav-item" (click)="selectedNavItem(1)">nav 1 (click me)</div> <div class="nav-item" (click)="selectedNavItem(2)">nav 2 (click me)</div> `, }) export class Navigation { item:number; constructor(private _navService:NavService) {} selectedNavItem(item: number) { console.log('selected nav item ' + item); this._navService.changeNav(item); } } 

Plunker


另请参阅组件交互手册示例 ,该手册除了使用可观察对象外还使用了Subject 。 虽然这个例子是“父母和孩子的沟通”,但是相同的技术适用于不相关的组件。

突发新闻:我添加了另一个使用Observable而不是EventEmitter的答案。 我build议这个答案。 实际上,在服务中使用EventEmitter是不好的做法 。


原文答案:(不要这样做)

将EventEmitter放入一个服务中,该服务允许Obse​​rvingComponent直接订阅(和取消订阅)事件

 import {EventEmitter} from 'angular2/core'; export class NavService { navchange: EventEmitter<number> = new EventEmitter(); constructor() {} emit(number) { this.navchange.emit(number); } subscribe(component, callback) { // set 'this' to component when callback is called return this.navchange.subscribe(data => call.callback(component, data)); } } @Component({ selector: 'obs-comp', template: 'obs component, index: {{index}}' }) export class ObservingComponent { item: number; subscription: any; constructor(private navService:NavService) { this.subscription = this.navService.subscribe(this, this.selectedNavItem); } selectedNavItem(item: number) { console.log('item index changed!', item); this.item = item; } ngOnDestroy() { this.subscription.unsubscribe(); } } @Component({ selector: 'my-nav', template:` <div class="nav-item" (click)="selectedNavItem(1)">item 1 (click me)</div> `, }) export class Navigation { constructor(private navService:NavService) {} selectedNavItem(item: number) { console.log('selected nav item ' + item); this.navService.emit(item); } } 

如果你尝试了Plunker ,那么我不喜欢这种方法:

  • ObservingComponent在销毁时需要取消订阅
  • 我们必须将组件传递给subscribe()以便在调用callback时设置正确的值

更新:解决第二个项目符号的替代scheme是让ObservingComponent直接订阅navchange EventEmitter属性:

 constructor(private navService:NavService) { this.subscription = this.navService.navchange.subscribe(data => this.selectedNavItem(data)); } 

如果我们直接订阅,那么我们不需要NavService上的subscribe()方法。

为了使NavService稍微封装一些,你可以添加一个getNavChangeEmitter()方法并使用它:

 getNavChangeEmitter() { return this.navchange; } // in NavService constructor(private navService:NavService) { // in ObservingComponent this.subscription = this.navService.getNavChangeEmitter().subscribe(data => this.selectedNavItem(data)); } 

您需要在ObservingComponent的模板中使用导航组件(不要忘记添加一个select器导航组件导航组件为前)

 <navigation-component (navchange)='onNavGhange($event)'></navigation-component> 

并在ObservingComponent中实现onNavGhange()

 onNavGhange(event) { console.log(event); } 

最后的事情..你不需要在@Componennt的事件属性

 events : ['navchange'], 

如果想要采用更具react native的编程风格,那么肯定会出现“Everything is a stream”的概念,因此,使用Observables来尽可能多地处理这些stream。

你可以使用上面描述的BehaviourSubject,或者还有一种方法:

你可以像这样处理EventEmitter: 首先添加一个select器

 import {Component, Output, EventEmitter} from 'angular2/core'; @Component({ // other properties left out for brevity selector: 'app-nav-component', //declaring selector template:` <div class="nav-item" (click)="selectedNavItem(1)"></div> ` }) export class Navigation { @Output() navchange: EventEmitter<number> = new EventEmitter(); selectedNavItem(item: number) { console.log('selected nav item ' + item); this.navchange.emit(item) } } 

现在,您可以像处理这个事件一样 让我们假设observer.component.html是Observer组件的视图

 <app-nav-component (navchange)="recieveIdFromNav($event)"></app-nav-component> 

然后在ObservingComponent.ts中

 export class ObservingComponent { //method to recieve the value from nav component public recieveIdFromNav(id: number) { console.log('here is the id sent from nav component ', id); } } 

我find了另一种解决scheme,而不使用Reactivex这两种服务。 我真的很喜欢rxjx API,但我认为解决asynchronous和/或复杂function时效果最好。 这样使用它,它的漂亮超过了我。

我想你正在寻找的是一个广播。 只是。 我发现这个解决scheme:

 <app> <app-nav (selectedTab)="onSelectedTab($event)"></app-nav> // This component bellow wants to know when a tab is selected // broadcast here is a property of app component <app-interested [broadcast]="broadcast"></app-interested> </app> @Component class App { broadcast: EventEmitter<tab>; constructor() { this.broadcast = new EventEmitter<tab>(); } onSelectedTab(tab) { this.broadcast.emit(tab) } } @Component class AppInterestedComponent implements OnInit { broadcast: EventEmitter<Tab>(); doSomethingWhenTab(tab){ ... } ngOnInit() { this.broadcast.subscribe((tab) => this.doSomethingWhenTab(tab)) } } 

这是一个完整的工作示例: https : //plnkr.co/edit/xGVuFBOpk2GP0pRBImsE

Interesting Posts