委托:在Angular中使用EventEmitter或Observable。
委托:在Angular中使用EventEmitter或Observable。
我正在尝试在 Angular 中实现类似委托模式的东西。当用户点击 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!'); } }
关键问题是,如何使观察组件观察到这个事件?
突发新闻:我添加了另一个答案,该答案使用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)); }
如果我们直接订阅,那么就不需要在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)); }
更新 2016-06-27:不要使用Observables,可以使用以下任何一种方法中的一种:
- BehaviorSubject,由 @Abdulrahman 推荐,或
- ReplaySubject,由 @Jason Goemaat 推荐
A Subject 既是 Observable(所以我们可以对其进行订阅),也是 Observer(所以我们可以调用 next() 来发出新值)。我们利用这个特性。Subject 允许多个观察者的值广播。我们不利用这个特性(我们只有一个观察者)。
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); } }
使用 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); } }
请参阅 组件交互教程示例,其中除了 observables 之外还使用了 Subject。虽然示例是“父子通信”,但是同样的技术也适用于不相关的组件。