我试图在Angular中实现类似委托模式的东西.当用户点击a时nav-item
,我想调用一个函数然后发出一个事件,而该事件又由一些其他组件监听事件来处理.
这是场景:我有一个Navigation
组件:
import {Component, Output, EventEmitter} from 'angular2/core';
@Component({
// other properties left out for brevity
events : ['navchange'],
template:`
`
})
export class Navigation {
@Output() navchange: EventEmitter = 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
一个BehaviorSubject,由@Abdulrahman在评论中推荐,或者
一个ReplaySubject,由@Jason Goemaat在评论中推荐
一个主题既是可观察到的(所以我们可以subscribe()
把它)和观察员(所以我们可以调用next()
它来发出一个新值).我们利用此功能.Subject允许将值多播到许多观察者.我们不利用此功能(我们只有一个Observer).
BehaviorSubject是Subject的变体.它具有"当前价值"的概念.我们利用这个:每当我们创建一个ObservingComponent时,它会自动从BehaviorSubject获取当前导航项值.
下面的代码和plunker使用BehaviorSubject.
ReplaySubject是Subject的另一种变体.如果要等到实际生成值,请使用ReplaySubject(1)
.虽然BehaviorSubject需要一个初始值(将立即提供),但ReplaySubject却没有.ReplaySubject将始终提供最新值,但由于它没有必需的初始值,因此服务可以在返回其第一个值之前执行一些异步操作.它仍会在具有最新值的后续呼叫中立即触发.如果您只想要一个值,请使用first()
订阅.如果您使用,则无需取消订阅first()
.
import {Injectable} from '@angular/core'
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
@Injectable()
export class NavService {
// Observable navItem source
private _navItemSource = new BehaviorSubject(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:`
nav 1 (click me)
nav 2 (click me)`
})
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实现不同,此实现还存储当前navItem
在服务中选择的实现,以便在创建观察组件时,它可以通过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;
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:`
nav 1 (click me)
nav 2 (click me)
`,
})
export class Navigation {
item:number;
constructor(private _navService:NavService) {}
selectedNavItem(item: number) {
console.log('selected nav item ' + item);
this._navService.changeNav(item);
}
}
Plunker
另请参阅Component Interaction Cookbook示例,该示例Subject
除了observable之外还使用了它.虽然该示例是"父母和子女的交流",但相同的技术适用于不相关的组件.
突发新闻:我添加了另一个使用Observable而不是EventEmitter的答案.我建议回答这个问题.实际上,在服务中使用EventEmitter是不好的做法.
原答案:(不要这样做)
将EventEmitter放入一个服务中,该服务允许ObservingComponent直接订阅(和取消订阅)该事件:
import {EventEmitter} from 'angular2/core'; export class NavService { navchange: EventEmitter= 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:` item 1 (click me)`, }) export class Navigation { constructor(private navService:NavService) {} selectedNavItem(item: number) { console.log('selected nav item ' + item); this.navService.emit(item); } }
如果您尝试使用Plunker此方法,我可能会对此方法不满意:
ObservingComponent需要在销毁时取消订阅
我们必须传递组件,subscribe()
以便this
在调用回调时设置正确
更新:解决第二个问题的替代方法是让ObservingComponent直接订阅navchange
EventEmitter属性:
constructor(private navService:NavService) { this.subscription = this.navService.navchange.subscribe(data => this.selectedNavItem(data)); }
如果我们直接订阅,那么我们就不需要subscribe()
NavService上的方法了.
为了使NavService更加封装,您可以添加一个getNavChangeEmitter()
方法并使用它:
getNavChangeEmitter() { return this.navchange; } // in NavService constructor(private navService:NavService) { // in ObservingComponent this.subscription = this.navService.getNavChangeEmitter().subscribe(data => this.selectedNavItem(data)); }