代表团: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放入一个服务中,该服务允许ObservingComponent直接订阅(和取消订阅)事件 :
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