239 votes

Événements globaux dans Angular

N'y a-t-il pas un équivalent à $scope.emit() o $scope.broadcast() dans Angular ?

Je sais que le EventEmitter mais, d'après ce que j'ai compris, cela ne fera qu'émettre un événement vers l'élément HTML parent.

Que faire si j'ai besoin de communiquer entre frères et sœurs fx. ou entre un composant situé dans la racine du DOM et un élément imbriqué à plusieurs niveaux de profondeur ?

2 votes

J'avais une question similaire concernant la création d'un composant de dialogue auquel on pouvait accéder à partir de n'importe quel point du domaine : stackoverflow.com/questions/34572539/ En gros, une solution consiste à placer un émetteur d'événements dans un service

1 votes

Voici mon implémentation d'un tel service en utilisant RXJS qui permet d'obtenir les nièmes dernières valeurs sur abonnement. stackoverflow.com/questions/46027693/

409voto

pixelbits Points 5710

Il n'y a pas d'équivalent à $scope.emit() o $scope.broadcast() d'AngularJS. EventEmitter à l'intérieur d'un composant s'en rapproche, mais comme vous l'avez mentionné, il n'émettra un événement que vers le composant parent immédiat.

Dans Angular, il existe d'autres alternatives que je vais essayer d'expliquer ci-dessous.

Les liaisons @Input() permettent de connecter le modèle d'application dans un graphe d'objets dirigé (de la racine aux feuilles). Le comportement par défaut de la stratégie de détection des changements d'un composant consiste à propager tous les changements apportés à un modèle d'application pour tous les bindings de tout composant connecté.

Aside : Il existe deux types de modèles : Les modèles de vue et les modèles d'application. Un modèle d'application est relié par des liaisons @Input(). Un modèle de vue est simplement une propriété de composant (non décorée avec @Input()) qui est liée dans le modèle du composant.

Pour répondre à vos questions :

Que faire si j'ai besoin de communiquer entre les composants d'une fratrie ?

  1. Modèle d'application partagée : Les frères et sœurs peuvent communiquer par le biais d'un modèle d'application partagé (tout comme angular 1). Par exemple, lorsqu'un membre de la fratrie apporte une modification à un modèle, l'autre membre de la fratrie qui a des liens avec le même modèle est automatiquement mis à jour.

  2. Événements liés aux composants : Les composants enfants peuvent émettre un événement vers le composant parent en utilisant des liaisons @Output(). Le composant parent peut gérer l'événement, et manipuler le modèle de l'application ou son propre modèle de vue. Les modifications apportées au modèle d'application sont automatiquement propagées à tous les composants qui se lient directement ou indirectement au même modèle.

  3. Événements de service : Les composants peuvent s'abonner à des événements de service. Par exemple, deux composants frères et sœurs peuvent s'abonner au même événement de service et répondre en modifiant leurs modèles respectifs. Plus d'informations à ce sujet ci-dessous.

Comment puis-je communiquer entre un composant racine et un composant imbriqué à plusieurs niveaux de profondeur ?

  1. Modèle d'application partagée : Le modèle d'application peut être transmis du composant Root vers les sous-composants profondément imbriqués par le biais de liaisons @Input(). Les modifications apportées à un modèle à partir de n'importe quel composant se propagent automatiquement à tous les composants qui partagent le même modèle.
  2. Événements de service : Vous pouvez également déplacer l'EventEmitter vers un service partagé, ce qui permet à tout composant d'injecter le service et de s'abonner à l'événement. De cette façon, un composant racine peut appeler une méthode du service (généralement en modifiant le modèle), qui à son tour émet un événement. Plusieurs couches plus bas, un composant petit-enfant qui a également injecté le service et souscrit au même événement, peut le traiter. Tout gestionnaire d'événement qui modifie un modèle d'application partagé se propage automatiquement à tous les composants qui en dépendent. C'est probablement l'équivalent le plus proche de $scope.broadcast() d'Angular 1. La section suivante décrit cette idée plus en détail.

Exemple d'un service observable qui utilise les événements du service pour propager les changements.

Voici un exemple de service observable qui utilise les événements de service pour propager les changements. Lorsqu'un TodoItem est ajouté, le service émet un événement notifiant les abonnés de ses composants.

export class TodoItem {
    constructor(public name: string, public done: boolean) {
    }
}
export class TodoService {
    public itemAdded$: EventEmitter<TodoItem>;
    private todoList: TodoItem[] = [];

    constructor() {
        this.itemAdded$ = new EventEmitter();
    }

    public list(): TodoItem[] {
        return this.todoList;
    }

    public add(item: TodoItem): void {
        this.todoList.push(item);
        this.itemAdded$.emit(item);
    }
}

Voici comment un composant Root s'abonnerait à l'événement :

export class RootComponent {
    private addedItem: TodoItem;
    constructor(todoService: TodoService) {
        todoService.itemAdded$.subscribe(item => this.onItemAdded(item));
    }

    private onItemAdded(item: TodoItem): void {
        // do something with added item
        this.addedItem = item;
    }
}

Un composant enfant imbriqué à plusieurs niveaux s'abonnerait à l'événement de la même manière :

export class GrandChildComponent {
    private addedItem: TodoItem;
    constructor(todoService: TodoService) {
        todoService.itemAdded$.subscribe(item => this.onItemAdded(item));
    }

    private onItemAdded(item: TodoItem): void {
        // do something with added item
        this.addedItem = item;
    }
}

Voici le composant qui appelle le service pour déclencher l'événement (il peut se trouver n'importe où dans l'arbre des composants) :

@Component({
    selector: 'todo-list',
    template: `
         <ul>
            <li *ngFor="#item of model"> {{ item.name }}
            </li>
         </ul>
        <br />
        Add Item <input type="text" #txt /> <button (click)="add(txt.value); txt.value='';">Add</button>
    `
})
export class TriggeringComponent{
    private model: TodoItem[];

    constructor(private todoService: TodoService) {
        this.model = todoService.list();
    }

    add(value: string) {
        this.todoService.add(new TodoItem(value, false));
    }
}

Référence : Détection des changements en Angular

30 votes

J'ai vu la queue $ dans quelques messages maintenant pour un observable ou un EventEmitter -- par exemple, itemAdded$ . Est-ce une convention RxJS ou autre ? D'où cela vient-il ?

1 votes

Bonne réponse. Vous avez déclaré : "Les modifications apportées au modèle d'application sont automatiquement propagées à tous les composants qui se lient directement ou indirectement au même modèle." J'ai l'intuition que cela ne fonctionne pas tout à fait de cette façon (mais je n'en suis pas sûr). L'autre solution de Savkin article de blog donne un exemple d'un composant modifiant le street du modèle d'application, mais comme Angular 2 met en œuvre la détection des changements par identité/référence, aucun changement n'est propagé ( onChanges n'est pas appelé), car la référence du modèle d'application n'a pas changé (suite...)

0 votes

Je vais essayer de trouver où j'ai trouvé la convention $. Pour être honnête, je ne me souviens pas où, mais j'ai trouvé cette convention utile (j'espère que c'est une convention recommandée par la communauté).

52voto

jim.taylor.1974 Points 551

Le code suivant est un exemple de remplacement de l'option $scope.emit() o $scope.broadcast() dans Angular 2 en utilisant un service partagé pour gérer les événements.

import {Injectable} from 'angular2/core';
import * as Rx from 'rxjs/Rx';

@Injectable()
export class EventsService {
    constructor() {
        this.listeners = {};
        this.eventsSubject = new Rx.Subject();

        this.events = Rx.Observable.from(this.eventsSubject);

        this.events.subscribe(
            ({name, args}) => {
                if (this.listeners[name]) {
                    for (let listener of this.listeners[name]) {
                        listener(...args);
                    }
                }
            });
    }

    on(name, listener) {
        if (!this.listeners[name]) {
            this.listeners[name] = [];
        }

        this.listeners[name].push(listener);
    }

    off(name, listener) {
        this.listeners[name] = this.listeners[name].filter(x => x != listener);
    }

    broadcast(name, ...args) {
        this.eventsSubject.next({
            name,
            args
        });
    }
}

Exemple d'utilisation :

Diffusion :

function handleHttpError(error) {
    this.eventsService.broadcast('http-error', error);
    return ( Rx.Observable.throw(error) );
}

Auditeur :

import {Inject, Injectable} from "angular2/core";
import {EventsService}      from './events.service';

@Injectable()
export class HttpErrorHandler {
    constructor(eventsService) {
        this.eventsService = eventsService;
    }

    static get parameters() {
        return [new Inject(EventsService)];
    }

    init() {
        this.eventsService.on('http-error', function(error) {
            console.group("HttpErrorHandler");
            console.log(error.status, "status code detected.");
            console.dir(error);
            console.groupEnd();
        });
    }
}

Il peut soutenir plusieurs arguments :

this.eventsService.broadcast('something', "Am I a?", "Should be b", "C?");

this.eventsService.on('something', function (a, b, c) {
   console.log(a, b, c);
});

0 votes

Qu'est-ce que cela fait ? static get parameters() { return [new Inject(EventsService)] ; }

0 votes

Dans cet exemple, j'utilise le cadre Ionic 2. La méthode des paramètres statiques est appelée lorsque la méthode du constructeur est invoquée et est utilisée pour fournir les dépendances au constructeur. Explication ici stackoverflow.com/questions/35919593/

2 votes

Bien fait. Il est simple et fournit un système de notification facilement adaptable à l'ensemble de l'application, et pas seulement à une seule.

18voto

t.888 Points 2619

J'utilise un service de messages qui englobe une application rxjs. Subject (TypeScript)

Exemple de Plunker : Service de messages

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/filter'
import 'rxjs/add/operator/map'

interface Message {
  type: string;
  payload: any;
}

type MessageCallback = (payload: any) => void;

@Injectable()
export class MessageService {
  private handler = new Subject<Message>();

  broadcast(type: string, payload: any) {
    this.handler.next({ type, payload });
  }

  subscribe(type: string, callback: MessageCallback): Subscription {
    return this.handler
      .filter(message => message.type === type)
      .map(message => message.payload)
      .subscribe(callback);
  }
}

Les composants peuvent s'abonner et diffuser des événements (expéditeur) :

import { Component, OnDestroy } from '@angular/core'
import { MessageService } from './message.service'
import { Subscription } from 'rxjs/Subscription'

@Component({
  selector: 'sender',
  template: ...
})
export class SenderComponent implements OnDestroy {
  private subscription: Subscription;
  private messages = [];
  private messageNum = 0;
  private name = 'sender'

  constructor(private messageService: MessageService) {
    this.subscription = messageService.subscribe(this.name, (payload) => {
      this.messages.push(payload);
    });
  }

  send() {
    let payload = {
      text: `Message ${++this.messageNum}`,
      respondEvent: this.name
    }
    this.messageService.broadcast('receiver', payload);
  }

  clear() {
    this.messages = [];
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

(récepteur)

import { Component, OnDestroy } from '@angular/core'
import { MessageService } from './message.service'
import { Subscription } from 'rxjs/Subscription'

@Component({
  selector: 'receiver',
  template: ...
})
export class ReceiverComponent implements OnDestroy {
  private subscription: Subscription;
  private messages = [];

  constructor(private messageService: MessageService) {
    this.subscription = messageService.subscribe('receiver', (payload) => {
      this.messages.push(payload);
    });
  }

  send(message: {text: string, respondEvent: string}) {
    this.messageService.broadcast(message.respondEvent, message.text);
  }

  clear() {
    this.messages = [];
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

El subscribe méthode de MessageService renvoie un rxjs Subscription qui peut être désabonné de la manière suivante :

import { Subscription } from 'rxjs/Subscription';
...
export class SomeListener {
  subscription: Subscription;

  constructor(private messageService: MessageService) {
    this.subscription = messageService.subscribe('someMessage', (payload) => {
      console.log(payload);
      this.subscription.unsubscribe();
    });
  }
}

Voir aussi cette réponse : https://stackoverflow.com/a/36782616/1861779

Exemple de Plunker : Service de messages

13voto

Danial Kalbasi Points 396

NE PAS UTILISER EventEmitter pour la communication de votre service.

Vous devez utiliser l'un des types Observable. Personnellement, j'aime bien BehaviorSubject.

Un exemple simple :

Vous pouvez passer l'état initial, ici je passe null.

let subject = new BehaviorSubject(null) ;

Lorsque vous voulez mettre à jour le sujet

subject.next(myObject)

Observer à partir de n'importe quel service ou composant et agir lorsqu'il reçoit de nouvelles mises à jour.

sujet.subscribe(this.YOURMETHOD) ;

Voici de plus amples informations. .

8voto

Günter Zöchbauer Points 21340

Vous pouvez utiliser EventEmitter ou observables pour créer un service de bus d'événements que vous enregistrez auprès de DI. Chaque composant qui veut participer demande simplement le service comme paramètre de constructeur et émet et/ou s'abonne aux événements.

Voir aussi

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X