委派:Angular中的EventEmitter或Observable

我正在尝试在Angular中实现类似委托模式的东西。 当用户单击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!');
}


}

关键问题是,如何让观察组件观察问题中的事件?

179029 次浏览

你需要在ObservingComponent的模板中使用Navigation组件(别忘了给Navigation组件添加选择器 .)。ex的导航组件)

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

并在ObservingComponent中实现onNavGhange()

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

最后一点…你不需要@Componennt中的events属性

events : ['navchange'],

突发新闻:我添加了使用Observable而不是EventEmitter的另一个答案。我推荐这个答案而不是这个。实际上,在服务中使用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);
}
}

如果你尝试< a href = " http://plnkr.co/edit/wzN7ZKU1CmVwbtxw8XFJ?p =预览noreferrer“rel = >恰好< / >,有一些事情我不喜欢这种方法:

  • 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:而不是使用可观察对象,使用任何一个

  • 一个行为主体,正如@Abdulrahman在评论中推荐的那样,或者
  • @Jason Goemaat在评论中推荐的ReplaySubject

主题既是一个可观察对象(所以我们可以对它进行subscribe()),也是一个观察者(所以我们可以对它调用next()来发出一个新值)。我们利用了这个特性。一个Subject允许将值多播到多个观察者。我们没有利用这个特性(我们只有一个观察者)。

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

下面的代码和砰砰作响使用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<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);
}
}

< a href = " http://plnkr.co/edit/XqwwUM44NQEpxQVFFxNW?p =预览noreferrer“rel = >恰好< / >


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

这里有一个使用Observable 而不是EventEmitter的实现。与我的EventEmitter实现不同,该实现还将当前选择的navItem存储在服务中,因此当创建观察组件时,它可以通过API调用navItem()检索当前值,然后通过navChange$可观察对象通知更改。

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);
}
}

< a href = " http://plnkr.co/edit/vL76b0UjrAav3Ao7kF4W?p =预览noreferrer“rel = >恰好< / >


另请参阅组件交互烹饪书示例,除可观察对象外,它还使用Subject。虽然示例是“父和子通信”,但同样的技术也适用于不相关的组件。

我找到了另一个解决方案,没有使用Reactivex服务。我其实很喜欢rxjx API,但我认为它在解决异步和/或复杂函数时最好。用这种方式使用它,对我来说太过分了。

我想你想要的是广播。只是这一点。我找到了这个解决方案:

<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 < / p >

如果一个人想要遵循更面向响应式的编程风格,那么“一切都是流”的概念肯定会出现,因此,尽可能多地使用可观察对象来处理这些流。

你可以使用如上所述的behavisubject或还有一种方法:

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

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);
}


}

你可以使用任何一种:

  1. 行为主体:

BehaviorSubject是一种主体类型,主体是一种特殊类型的可观察对象,可以充当被观察对象和观察者 你可以像订阅其他可观察对象一样订阅消息,在订阅时,它会返回主题的最后一个值 由源可观察对象触发:

优点:在组件之间传递数据不需要亲子关系等关系。

导航服务

import {Injectable}      from '@angular/core'
import {BehaviorSubject} from 'rxjs/BehaviorSubject';


@Injectable()
export class NavService {
private navSubject$ = new BehaviorSubject<number>(0);


constructor() {  }


// Event New Item Clicked
navItemClicked(navItem: number) {
this.navSubject$.next(number);
}


// Allowing Observer component to subscribe emitted data only
getNavItemClicked$() {
return this.navSubject$.asObservable();
}
}

导航组件

@Component({
selector: 'navbar-list',
template:`
<ul>
<li><a (click)="navItemClicked(1)">Item-1 Clicked</a></li>
<li><a (click)="navItemClicked(2)">Item-2 Clicked</a></li>
<li><a (click)="navItemClicked(3)">Item-3 Clicked</a></li>
<li><a (click)="navItemClicked(4)">Item-4 Clicked</a></li>
</ul>
})
export class Navigation {
constructor(private navService:NavService) {}
navItemClicked(item: number) {
this.navService.navItemClicked(item);
}
}

观察组件

@Component({
selector: 'obs-comp',
template: `obs component, item: \{\{item}}`
})
export class ObservingComponent {
item: number;
itemClickedSubcription:any


constructor(private navService:NavService) {}
ngOnInit() {


this.itemClickedSubcription = this.navService
.getNavItemClicked$
.subscribe(
item => this.selectedNavItem(item)
);
}
selectedNavItem(item: number) {
this.item = item;
}


ngOnDestroy() {
this.itemClickedSubcription.unsubscribe();
}
}

第二个方法是Event Delegation in upward direction child -> parent

  1. 使用@Input和@Output装饰器,父组件将数据传递给子组件,子组件通知父组件

回答:@Ashish Sharma。