Délégation: EventEmitter ou Observable en angle

j'essaie de mettre en œuvre quelque chose comme un modèle de délégation en angle. Lorsque l'utilisateur clique sur un nav-item , je voudrais appeler une fonction qui émet alors un événement qui devrait être gérée par une autre composante de l'écoute de l'événement.

Voici le scénario: j'ai un Navigation composant:

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

}

voici le composant d'observation:

export class ObservingComponent {

  // How do I observe the event ? 
  // <----------Observe/Register Event ?-------->

  public selectedNavItem(item: number) {
    console.log('item index changed!');
  }

}

la question clé est, comment est-ce que la composante observation observe l'événement en question ?

177
demandé sur yurzui 2015-12-20 03:32:34

6 réponses

mise à jour 2016-06-27: au lieu d'utiliser des Observables, utilisez soit

  • un BehaviorSubject, comme recommandé par @Abdulrahman dans un commentaire, ou
  • un ReplaySubject, comme recommandé par @Jason Goemaat dans un commentaire

a sujet est à la fois un Observable (donc nous pouvons subscribe() à elle) et un observateur (donc nous pouvons appeler next() sur elle pour émettre une nouvelle valeur). Nous exploitons cette caractéristique. Un sujet permet à des valeurs d'être multicastées à de nombreux observateurs. Nous n'exploitons pas cette fonctionnalité (nous n'avons qu'un observateur).

BehaviorSubject est une variante du sujet. Il a la notion de "valeur actuelle". Nous exploitons ceci: chaque fois que nous créons un ObservingComponent, il obtient automatiquement la valeur courante de l'item de navigation du BehaviorSubject.

Le code ci-dessous et le plunker use BehaviorSubject.

ReplaySubject est une autre variante du sujet. Si vous voulez attendre qu'une valeur soit réellement produite, utilisez ReplaySubject(1) . Alors qu'un BehaviorSubject nécessite une valeur initiale (qui sera fournie immédiatement), ReplaySubject ne le fait pas. ReplaySubject fournira toujours la valeur la plus récente, mais comme il n'a pas de valeur initiale requise, le service peut faire une opération async avant de retourner sa première valeur. Il continuera à tirer immédiatement sur les appels suivants avec la valeur la plus récente. Si vous voulez juste une valeur, utilisez first() sur l'abonnement. Vous n'avez pas à vous désabonner si vous utilisez 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


réponse originale qui utilise un Observable: (il faut plus de code et de logique que d'utiliser un BehaviorSubject, donc je ne le recommande pas, mais il peut être instructif)

donc, voici une implémentation qui utilise un Observable au lieu d'un EventEmitter . Contrairement à l'implémentation de mon EventEmitter, cette implémentation stocke également le navItem actuellement sélectionné dans le service, de sorte que lorsqu'un composant d'observation est créé, il peut récupérer la valeur actuelle via un appel API. navItem() , puis être informé des modifications via le 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


Voir Aussi Le Component Interaction Cookbook example , qui utilise un Subject en plus des observables. Bien que l'exemple soit "communication entre parents et enfants", la même technique s'applique à des situations non reliées entre elles. composant.

370
répondu Mark Rajcok 2017-05-23 11:47:26

Breaking news: j'ai ajouté autre réponse qui utilise un Observables plutôt que d'une EventEmitter. Je recommande cette réponse plutôt que celle-ci. Et en fait, utiliser un EventEmitter dans un service est mauvaise pratique .


réponse Originale à cette question: (ne pas faire)

mettre L'EventEmitter en service, ce qui permet à la ObservingComponent de s'abonner directement (et de se désinscrire) à l'événement :

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

si vous essayez le Plunker , il y a quelques choses que je n'aime pas dans cette approche:

  • ObservingComponent besoin de se désabonner quand il est détruit
  • nous devons passer le composant à subscribe() de sorte que le this approprié est mis lorsque le le rappel s'appelle

mise à Jour: Une alternative qui permet de résoudre le 2e point est d'avoir le ObservingComponent s'abonner directement à la navchange EventEmitter la propriété:

constructor(private navService:NavService) {
   this.subscription = this.navService.navchange.subscribe(data =>
     this.selectedNavItem(data));
}

si nous nous abonnons directement, nous n'aurions pas besoin de la méthode subscribe() sur le NavService.

pour rendre le NavService un peu plus encapsulé, vous pouvez ajouter une méthode getNavChangeEmitter() et utiliser que:

getNavChangeEmitter() { return this.navchange; }  // in NavService

constructor(private navService:NavService) {  // in ObservingComponent
   this.subscription = this.navService.getNavChangeEmitter().subscribe(data =>
     this.selectedNavItem(data));
}
30
répondu Mark Rajcok 2017-05-23 11:47:26

si l'on veut suivre un style de programmation plus réactif, alors certainement le concept de" tout est un flux " entre en scène et, par conséquent, utiliser des Observables pour traiter ces flux aussi souvent que possible.

1
répondu kg11 2017-08-18 04:37:56

vous pouvez utiliser BehaviourSubject comme décrit ci-dessus ou il y a une autre façon:

vous pouvez gérer EventEmitter comme ça: ajouter d'abord un sélecteur

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

}

Maintenant vous pouvez gérer cet événement comme supposons observateur.composant.html est le point de vue de l'Observateur de la composante

<app-nav-component (navchange)="recieveIdFromNav($event)"></app-nav-component>

puis dans le 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
répondu Ashish Sharma 2017-10-05 09:43:27

vous devez utiliser le composant de Navigation dans le modèle de ObservingComponent ( n'oubliez pas d'ajouter un sélecteur au composant de Navigation .. navigation-composante ex )

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

Et de mettre en œuvre onNavGhange() dans ObservingComponent

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

dernière chose .. vous n'avez pas besoin de l'attribut events dans @Componennt

events : ['navchange'], 
0
répondu Mourad Zouabi 2015-12-20 10:23:59

j'ai trouvé une autre solution pour ce cas sans utiliser les services de Réactivex. En fait, j'adore L'API rxjx, mais je pense qu'elle va le mieux dans la résolution d'une fonction asynchrone et/ou complexe. L'utiliser de cette façon, c'est assez dépassé pour moi.

ce que je pense que vous cherchez, c'est une émission. Juste que. Et j'ai trouvé cette solution:

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

ceci est un exemple de travail complet: https://plnkr.co/edit/xGVuFBOpk2GP0pRBImsE

-2
répondu Nicholas Marcaccini Augusto 2016-12-13 12:51:03