web-dev-qa-db-ja.com

委任:EventEmitterまたは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!');
  }

}

重要な問題は、どのようにして監視コンポーネントに問題のイベントを監視させるのですか?

206
the_critic

更新2016-06-27:の代わりにObservablesを使う

  • @Abdulrahmanのコメントで推奨されているBehaviorSubject
  • コメント内の@Jason Goemaatさんが推奨するReplaySubject

A Subject はObservable(それでsubscribe()ができる)とObserver(それでnext()を呼び出して新しい値を出すことができる)の両方です。この機能を利用します。 Subjectは、値を多くのObserverにマルチキャストすることを許可します。我々はこの機能を利用していません(Observerは1つだけです)。

BehaviorSubject はSubjectの一種です。それは「現在価値」という概念を持っています。これを悪用します。ObservingComponentを作成するたびに、BehaviorSubjectから現在のナビゲーション項目の値が自動的に取得されます。

下記のコードと plunker はBehaviorSubjectを使います。

ReplaySubject はSubjectの他の変形です。値が実際に生成されるまで待ちたい場合は、ReplaySubject(1)を使用してください。 BehaviorSubjectには初期値(すぐに提供される)が必要ですが、ReplaySubjectには必要ありません。 ReplaySubjectは常に最新の値を提供しますが、必要な初期値がないため、サービスは最初の値を返す前に非同期操作を行うことができます。それは最新の値でそれ以降の呼び出しでまだすぐに起動します。値を1つだけ指定したい場合は、サブスクリプションに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);
  }
}

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

Plunker


オブザーバブルに加えてSubjectを使う Component Interaction Cookbook example もご覧ください。例は「親子通信」ですが、関連のないコンポーネントにも同じ手法が適用できます。

415
Mark Rajcok

最新ニュース:EventEmitterではなくObservableを使う もう一つの答え を追加しました。私はこれに答えることをお勧めします。そして実際には、サービスで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は、破棄されたときに登録解除する必要があります。
  • コールバックが呼び出されたときに適切なthisが設定されるように、コンポーネントをsubscribe()に渡す必要があります。

更新:2番目の弾丸を解決する代替策は、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));
}
32
Mark Rajcok

もっとリアクティブ指向のプログラミングスタイルに従うことを望むなら、間違いなく「すべてはストリームである」という概念が浮かび上がるので、Observablesを使用してこれらのストリームをできるだけ頻繁に処理します。

1
kg11

あなたは上記のように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コンポーネントのビューであるとしましょう

<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
Ashish Sharma

次のいずれかを使用できます。

  1. 行動の件名:

BehaviorSubjectは件名の一種です。件名は特別なタイプのオブザーバブルで、他のオブザーバブルと同じようにメッセージを購読することができます。購読時には、ソースオブザーバブルから発行された件名の最後の値が返されます。

利点:コンポーネント間でデータを渡すために、親子関係などの関係が不要です。

NAVサービス

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

2番目のアプローチはEvent Delegation in upward direction child -> parentです

  1. @Inputおよび@Outputデコレータを使用して、親コンポーネントにデータを渡す親および子通知親コンポーネント

例:@Ashish Sharmaによる回答。

0
khizer

ObservingComponentのテンプレートでNavigationコンポーネントを使用する必要があります( selector をNavigationコンポーネントに追加することを忘れないでください。exの場合はnavigation-component)。

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

ObservingComponentにonNavGhange()を実装します。

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

最後に.. @Componennt にevents属性は必要ありません。

events : ['navchange'], 
0
Mourad Zouabi