委托:Angular 中的 EventEmitter 或 Observable
Delegation: EventEmitter or Observable in Angular
我正在尝试在 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!');
}
}
关键问题是,如何让观察组件观察到有问题的事件?
您需要在 ObservingComponent 的模板中使用 Navigation 组件(不要忘记将 selector 添加到 Navigation 组件 .. navigation-component for ex )
<navigation-component (navchange)='onNavGhange($event)'></navigation-component>
并在 ObservingComponent 中实现 onNavGhange()
onNavGhange(event) {
console.log(event);
}
最后一件事..你不需要@Componennt
中的事件属性
events : ['navchange'],
突发新闻:我已经添加了 that uses an Observable rather than an EventEmitter. I recommend that answer over this one. And actually, using an EventEmitter in a service is 。
原始答案:(不要这样做)
把EventEmitter放到一个service中,让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()
,以便在调用回调时设置正确的 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,而是使用
- @Abdulrahman 在评论中推荐的 BehaviorSubject,或者
- 一个 ReplaySubject,由@Jason Goemaat 在评论中推荐
A Subject 既是一个 Observable(所以我们可以 subscribe()
它)也是一个 Observer(所以我们可以调用 next()
它发出一个新值)。我们利用此功能。 Subject 允许将值多播给许多观察者。我们不利用这个特性(我们只有一个观察者)。
BehaviorSubject 是 Subject 的变体。它有"the current value"的概念。我们利用这一点:每当我们创建一个 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<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);
}
}
使用 Observable 的原始答案:(它比使用 BehaviorSubject 需要更多的代码和逻辑,所以我不推荐它,但它可能具有指导意义)
所以,这是一个使用 Observable 的实现。与我的 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<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);
}
}
另请参阅 Component Interaction Cookbook example,它使用 Subject
除了 observables。尽管示例是 "parent and children communication,",但相同的技术适用于不相关的组件。
我在不使用 Reactivex 这两种服务的情况下找到了针对这种情况的另一种解决方案。我实际上喜欢 rxjx API 但是我认为它在解析异步 and/or 复杂函数时效果最好。这么用,简直超出我的想象了
我想你要找的是广播。只是。我找到了这个解决方案:
<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
如果想遵循一种更面向 Reactive 的编程风格,那么 "Everything is a stream" 的概念肯定会出现,因此,尽可能多地使用 Observables 来处理这些流。
您可以如上所述使用 BehaviourSubject,或者还有一种方法:
您可以像这样处理 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组件的view
<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);
}
}
您可以使用:
- 行为主体:
BehaviorSubject是subject的一种,subject是一种特殊类型的observable,可以充当observable和observer
您可以像订阅任何其他可观察对象一样订阅消息,订阅后,它 returns 主题的最后一个值
源 Observable 发射:
优点:无需父子关系等组件间数据传递
导航服务
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
- 使用@Input 和@Output 装饰器,父组件将数据传递给子组件,子组件通知父组件
例如@Ashish Sharma 给出的回答。
我正在尝试在 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!');
}
}
关键问题是,如何让观察组件观察到有问题的事件?
您需要在 ObservingComponent 的模板中使用 Navigation 组件(不要忘记将 selector 添加到 Navigation 组件 .. navigation-component for ex )
<navigation-component (navchange)='onNavGhange($event)'></navigation-component>
并在 ObservingComponent 中实现 onNavGhange()
onNavGhange(event) {
console.log(event);
}
最后一件事..你不需要@Componennt
中的事件属性events : ['navchange'],
突发新闻:我已经添加了
原始答案:(不要这样做)
把EventEmitter放到一个service中,让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()
,以便在调用回调时设置正确的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,而是使用
- @Abdulrahman 在评论中推荐的 BehaviorSubject,或者
- 一个 ReplaySubject,由@Jason Goemaat 在评论中推荐
A Subject 既是一个 Observable(所以我们可以 subscribe()
它)也是一个 Observer(所以我们可以调用 next()
它发出一个新值)。我们利用此功能。 Subject 允许将值多播给许多观察者。我们不利用这个特性(我们只有一个观察者)。
BehaviorSubject 是 Subject 的变体。它有"the current value"的概念。我们利用这一点:每当我们创建一个 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<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);
}
}
使用 Observable 的原始答案:(它比使用 BehaviorSubject 需要更多的代码和逻辑,所以我不推荐它,但它可能具有指导意义)
所以,这是一个使用 Observable 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<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);
}
}
另请参阅 Component Interaction Cookbook example,它使用 Subject
除了 observables。尽管示例是 "parent and children communication,",但相同的技术适用于不相关的组件。
我在不使用 Reactivex 这两种服务的情况下找到了针对这种情况的另一种解决方案。我实际上喜欢 rxjx API 但是我认为它在解析异步 and/or 复杂函数时效果最好。这么用,简直超出我的想象了
我想你要找的是广播。只是。我找到了这个解决方案:
<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
如果想遵循一种更面向 Reactive 的编程风格,那么 "Everything is a stream" 的概念肯定会出现,因此,尽可能多地使用 Observables 来处理这些流。
您可以如上所述使用 BehaviourSubject,或者还有一种方法:
您可以像这样处理 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组件的view
<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);
}
}
您可以使用:
- 行为主体:
BehaviorSubject是subject的一种,subject是一种特殊类型的observable,可以充当observable和observer 您可以像订阅任何其他可观察对象一样订阅消息,订阅后,它 returns 主题的最后一个值 源 Observable 发射:
优点:无需父子关系等组件间数据传递
导航服务
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
- 使用@Input 和@Output 装饰器,父组件将数据传递给子组件,子组件通知父组件
例如@Ashish Sharma 给出的回答。