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!');
}
}
重要な問題は、どのようにして監視コンポーネントに問題のイベントを監視させるのですか?
更新2016-06-27:の代わりにObservablesを使う
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);
}
}
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);
}
}
オブザーバブルに加えてSubject
を使う Component Interaction Cookbook example もご覧ください。例は「親子通信」ですが、関連のないコンポーネントにも同じ手法が適用できます。
最新ニュース: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このアプローチについて私が気に入らないことがいくつかあります。
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));
}
もっとリアクティブ指向のプログラミングスタイルに従うことを望むなら、間違いなく「すべてはストリームである」という概念が浮かび上がるので、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コンポーネントのビューであるとしましょう
<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は件名の一種です。件名は特別なタイプのオブザーバブルで、他のオブザーバブルと同じようにメッセージを購読することができます。購読時には、ソースオブザーバブルから発行された件名の最後の値が返されます。
利点:コンポーネント間でデータを渡すために、親子関係などの関係が不要です。
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
です
例:@Ashish Sharmaによる回答。
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'],