我正在尝试在Angular中实现类似委托模式的东西。 当用户单击nav-item时,我想调用一个函数,该函数然后发出一个事件,该事件应该由侦听该事件的其他组件处理。

下面是场景:我有一个导航组件:

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

}

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


当前回答

突发新闻:我添加了另一个答案,使用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);
  }
}

如果你尝试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));
}

其他回答

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

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

并在ObservingComponent中实现onNavGhange()

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

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

events : ['navchange'], 

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

我找到了另一个解决方案,没有使用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

你可以使用如上所述的行为主体,或者还有一种方法:

你可以这样处理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);
 }

 }

更新2016-06-27:与其使用可观察对象,不如使用任意一种

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

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

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

下面的代码和活塞使用行为主体。

ReplaySubject is another variant of Subject. If you want to wait until a value is actually produced, use ReplaySubject(1). Whereas a BehaviorSubject requires an initial value (which will be provided immediately), ReplaySubject does not. ReplaySubject will always provide the most recent value, but since it does not have a required initial value, the service can do some async operation before returning it's first value. It will still fire immediately on subsequent calls with the most recent value. If you just want one value, use first() on the subscription. You do not have to unsubscribe if you use 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的实现。与我的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);
  }
}

砰砰作响


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