当前位置:  开发笔记 > 编程语言 > 正文

委托:Angular中的EventEmitter或Observable

如何解决《委托:Angular中的EventEmitter或Observable》经验,为你挑选了2个好方法。

我试图在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!');
  }

}

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



1> Mark Rajcok..:

更新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:`
    
    `
})
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:`
    
    
  `,
})
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之外还使用了它.虽然该示例是"父母和子女的交流",但相同的技术适用于不相关的组件.


如果要等到实际生成一个值,可以使用`ReplaySubject(1)`.`BehaviorSubject`需要一个初始值,它将立即提供.`ReplaySubject(1)`将始终提供最新值,但不需要初始值,因此服务可以在返回第一个值之前执行某些异步操作,但仍会在后续调用时立即触发最后一个值.如果您只对一个值感兴趣,可以在订阅上使用`first()`而不必在最后取消订阅,因为这样就完成了.
我建议使用[BehaviorSubject](http://reactivex.io/rxjs/manual/overview.html#behaviorsubject)而不是Observable.它更接近于'EventEmitter`,因为它"热"意味着它已经"共享",它被设计为保存当前值,最后它实现了Observable和Observer,它将为您节省至少五行代码和两个属性
在对Angular2问题的评论中反复提到,除了输出之外,不建议在任何地方使用`EventEmitter`.他们目前正在重写教程(服务器通信AFAIR),不鼓励这种做法.
有没有办法在上面的示例代码中初始化服务并从导航组件中触发第一个事件?问题是服务对象的`_observer`至少在被调用导航组件的`ngOnInit()`时没有被初始化.
@PankajParkar,关于"变更检测引擎如何知道可观察对象发生了变化" - 我删除了之前的评论回复.我最近了解到Angular没有修补`subscribe()`,因此无法检测到observable何时发生变化.通常,会触发一些异步事件(在我的示例代码中,它是按钮单击事件),相关的回调将在observable上调用next().但是由于异步事件而导致更改检测运行,而不是因为可观察到的更改.另见Günter评论:http://stackoverflow.com/a/36846501/215945

2> Mark Rajcok..:

突发新闻:我添加了另一个使用Observable而不是EventEmitter的答案.我建议回答这个问题.实际上,在服务中使用EventEmitter是不好的做法.


原答案:(不要这样做)

将EventEmitter放入一个服务中,该服务允许Obse​​rvingComponent直接订阅(和取消订阅)该事件:

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:`
    
  `,
})
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直接订阅navchangeEventEmitter属性:

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

推荐阅读
可爱的天使keven_464
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有