web-dev-qa-db-fra.com

Délégation: EventEmitter ou Observable in Angular

J'essaie d'implémenter quelque chose comme un modèle de délégation dans Angular. Lorsque l'utilisateur clique sur un nav-item, je souhaite appeler une fonction qui émet ensuite un événement qui devrait à son tour être géré par un autre composant à l'écoute de l'événement.

Voici le scénario: J'ai un composant 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)
    }

}

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 de savoir comment faire en sorte que la composante d'observation observe l'événement en question.

218
the_critic

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

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

Un Subject est à la fois un observable (nous pouvons donc subscribe()) et un observateur (nous pouvons donc appeler next() pour émettre une nouvelle valeur). Nous exploitons cette fonctionnalité. Un sujet permet aux valeurs d'être multicast à de nombreux observateurs. Nous n'exploitons pas cette fonctionnalité (nous n'avons qu'un observateur).

BehaviorSubject est une variante de Subject. Il a la notion de "la valeur actuelle". Nous exploitons ceci: chaque fois que nous créons un ObservingComponent, il récupère automatiquement la valeur de l’élément de navigation actuel du BehaviorSubject.

Le code ci-dessous et le plunker utilisent BehaviorSubject.

ReplaySubject est une autre variante de Subject. 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 n'en a pas besoin. ReplaySubject fournira toujours la valeur la plus récente, mais comme il n'a pas de valeur initiale requise, le service peut effectuer une opération asynchrone avant de renvoyer sa première valeur. Il sera toujours déclenché immédiatement lors des appels suivants avec la valeur la plus récente. Si vous ne voulez qu'une valeur, utilisez first() sur l'abonnement. Vous n'êtes pas obligé de 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 utilisant un observable: (cela nécessite plus de code et de logique que d'utiliser un BehaviorSubject, je ne le recommande donc pas, mais cela peut être instructif )

Donc, voici une implémentation qui utilise un observable au lieu d'un EventEmitter . Contrairement à mon implémentation EventEmitter, cette implémentation stocke également le navItem actuellement sélectionné dans le service. Ainsi, lorsqu'un composant d'observation est créé, il peut extraire la valeur actuelle via l'appel API navItem(), puis être averti 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 exemple de Component Interaction Cookbook , qui utilise un Subject en plus des observables. Bien que l'exemple soit "communication parents-enfants", la même technique s'applique aux composants non liés.

427
Mark Rajcok

Dernières nouvelles: J'ai ajouté ne autre réponse qui utilise un observable plutôt qu'un EventEmitter. Je recommande cette réponse sur celle-ci. Et en fait, utiliser un EventEmitter dans un service est mauvaise pratique .


Réponse originale: (ne fais pas ça)

Placez EventEmitter dans un service permettant à ObservingComponent de s'abonner (et de se désabonner) directement à 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 points que je n'aime pas dans cette approche:

  • ObservingComponent doit se désabonner lorsqu'il est détruit
  • nous devons passer le composant à subscribe() pour que le this correct soit défini lors de l'appel du rappel

Mise à jour: une alternative permettant de résoudre le problème suivant consiste à faire en sorte que le composant ObservingComponent souscrive directement à la propriété navchange EventEmitter:

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 service de navigation.

Pour rendre NavService légèrement plus encapsulé, vous pouvez ajouter une méthode getNavChangeEmitter() et l'utiliser:

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

constructor(private navService:NavService) {  // in ObservingComponent
   this.subscription = this.navService.getNavChangeEmitter().subscribe(data =>
     this.selectedNavItem(data));
}
33
Mark Rajcok

Si on veut suivre un style de programmation plus réactif, alors le concept de "Tout est un flux" entre en scène et utilise donc Observables pour traiter ces flux aussi souvent que possible.

1
kg11

vous pouvez utiliser BehaviourSubject comme décrit ci-dessus ou il existe un autre moyen:

vous pouvez gérer EventEmitter comme ceci: ajoutez 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)
}

}

Vous pouvez maintenant gérer cet événement commesupposons que observer.component.html soit la vue du composant Observer

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

Vous pouvez utiliser soit:

  1. Comportement comportemental:

BehaviorSubject est un type de sujet, un sujet est un type spécial d'observable qui peut agir en tant qu'observable et en tant qu'observateur, vous pouvez vous abonner à des messages comme n'importe quel autre observable et lors de votre inscription, il renvoie la dernière valeur du sujet émis par la source observable:

Avantage: Aucune relation telle qu'une relation parent-enfant n'est requise pour transmettre des données entre les composants.

NAV SERVICE

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

COMPOSANT DE NAVIGATION

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

OBSERVANT LE COMPOSANT

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

La deuxième approche est Event Delegation in upward direction child -> parent

  1. Utilisation du parent parent des décorateurs @Input et @Output pour transmettre des données au composant enfant et au composant parent notifiant

p.ex. Réponse donnée par @Ashish Sharma.

0
khizer

Vous devez utiliser le composant Navigation dans le modèle de ObservingComponent (n'oubliez pas d'ajouter un sélecteur au composant Navigation .. composant de navigation par exemple)

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

Et implémentez 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
Mourad Zouabi