Eventos globales en Angular

¿No hay equivalente a $scope.emit() o $scope.broadcast() en Angular?

Conozco la funcionalidad de EventEmitter , pero por lo que entiendo, solo emitirá un evento al elemento HTML principal.

¿Qué ocurre si necesito comunicarme entre fx? hermanos o entre un componente en la raíz del DOM y un elemento nested varios niveles de profundidad?

No hay equivalente a $scope.emit() o $scope.broadcast() de AngularJS. EventEmitter dentro de un componente se acerca, pero como mencionó, solo emitirá un evento al componente padre inmediato.

En Angular, hay otras alternativas que trataré de explicar a continuación.

Los enlaces @Input () permiten que el modelo de aplicación se conecte en un gráfico de objeto dirigido (raíz a hojas). El comportamiento predeterminado de la estrategia del detector de cambios de un componente es propagar todos los cambios a un modelo de aplicación para todas las vinculaciones de cualquier componente conectado.

Aparte: hay dos tipos de modelos: Modelos de Vista y Modelos de Aplicación. Un modelo de aplicación se conecta mediante enlaces @Input (). Un modelo de vista es solo una propiedad de componente (no decorado con @Input ()) que está enlazado en la plantilla del componente.

Para responder tu pregunta:

¿Qué pasa si necesito comunicarme entre componentes hermanos?

  1. Modelo de aplicación compartida : los hermanos pueden comunicarse a través de un modelo de aplicación compartido (como angular 1). Por ejemplo, cuando un hermano hace un cambio en un modelo, el otro hermano que tiene enlaces al mismo modelo se actualiza automáticamente.

  2. Component Events : los componentes secundarios pueden emitir un evento al componente principal utilizando enlaces @Output (). El componente principal puede manejar el evento y manipular el modelo de aplicación o su propio modelo de vista. Los cambios en el Modelo de Aplicación se propagan automáticamente a todos los componentes que directa o indirectamente se unen al mismo modelo.

  3. Eventos de servicio : los componentes pueden suscribirse a eventos de servicio. Por ejemplo, dos componentes hermanos pueden suscribirse al mismo evento de servicio y responder modificando sus respectivos modelos. Más sobre esto a continuación.

¿Cómo puedo comunicarme entre un componente Root y un componente nested en varios niveles?

  1. Modelo de aplicación compartida : el modelo de aplicación se puede pasar del componente raíz a subcomponentes profundamente nesteds a través de enlaces de @Input (). Los cambios en un modelo de cualquier componente se propagarán automáticamente a todos los componentes que comparten el mismo modelo.
  2. Eventos de servicio : también puede mover el EventEmitter a un servicio compartido, lo que le permite a cualquier componente inyectar el servicio y suscribirse al evento. De esta forma, un componente de raíz puede llamar a un método de servicio (generalmente mutando el modelo), que a su vez emite un evento. Varias capas hacia abajo, un componente de gran niño que también ha inyectado el servicio y se ha suscrito al mismo evento, puede manejarlo. Cualquier controlador de eventos que cambie un Modelo de Aplicación compartido, se propagará automáticamente a todos los componentes que dependan de él. Este es probablemente el equivalente más cercano a $scope.broadcast() de Angular 1. La siguiente sección describe esta idea con más detalle.

Ejemplo de un servicio observable que usa eventos de servicio para propagar cambios

Aquí hay un ejemplo de un servicio observable que usa eventos de servicio para propagar cambios. Cuando se agrega un TodoItem, el servicio emite un evento notificando a sus suscriptores de componentes.

 export class TodoItem { constructor(public name: string, public done: boolean) { } } export class TodoService { public itemAdded$: EventEmitter; 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); } } 

Aquí es cómo un componente raíz se suscribiría al evento:

 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 componente secundario nested en varios niveles se suscribiría al evento de la misma manera:

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

Aquí está el componente que llama al servicio para desencadenar el evento (puede residir en cualquier parte del árbol de componentes):

 @Component({ selector: 'todo-list', template: ` 
  • {{ item.name }}

Add Item ` }) export class TriggeringComponent{ private model: TodoItem[]; constructor(private todoService: TodoService) { this.model = todoService.list(); } add(value: string) { this.todoService.add(new TodoItem(value, false)); } }

Referencia: Detección de cambio en angular

El siguiente código es un ejemplo de reemplazo de $ scope.emit () o $ scope.broadcast () en Angular 2 utilizando un servicio compartido para manejar eventos.

 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); } broadcast(name, ...args) { this.eventsSubject.next({ name, args }); } } 

Ejemplo de uso:

Emisión:

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

Oyente:

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

Puede soportar múltiples argumentos:

 this.eventsService.broadcast('something', "Am I a?", "Should be b", "C?"); this.eventsService.on('something', function (a, b, c) { console.log(a, b, c); }); 

Estoy usando un servicio de mensajes que envuelve un Subject rxjs (TypeScript)

Ejemplo de Plunker: servicio de mensajes

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

Los componentes pueden suscribirse y emitir eventos (emisor):

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

(receptor)

 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 método de subscribe de MessageService devuelve un objeto de Subscription rxjs, que puede ser anulado como así:

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

También vea esta respuesta: https://stackoverflow.com/a/36782616/1861779

Ejemplo de Plunker: servicio de mensajes

NO use EventEmitter para su comunicación de servicio.

Debe usar uno de los tipos observables. Personalmente me gusta BehaviorSubject.

Ejemplo simple:

Puede pasar el estado inicial, aquí paso nulo

let subject = new BehaviorSubject (null);

Cuando quieres actualizar el tema

subject.next (myObject)

Observe desde cualquier servicio o componente y actúe cuando reciba nuevas actualizaciones.

subject.subscribe (this.YOURMETHOD);

Aquí hay más información. .

Puede usar EventEmitter u observables para crear un servicio eventbus que registre con DI. Cada componente que desea participar solo solicita el servicio como parámetro de constructor y emite y / o se suscribe a eventos.

Ver también

He creado una muestra de pub-sub aquí:

http://www.syntaxsuccess.com/viewarticle/pub-sub-in-angular-2.0

La idea es utilizar sujetos RxJs para conectar un Observer y Observables como una solución genérica para emitir y suscribirse a eventos personalizados. En mi ejemplo, uso un objeto de cliente para propósitos de demostración

 this.pubSubService.Stream.emit(customer); this.pubSubService.Stream.subscribe(customer => this.processCustomer(customer)); 

Aquí hay una demostración en vivo también: http://www.syntaxsuccess.com/angular-2-samples/#/demo/pub-sub

Mi forma favorita de hacerlo es usar el sujeto de comportamiento o el emisor de eventos (casi el mismo) en mi servicio para controlar todo mi subcomponente.

Usando cli angular, ejecutando ng gs para crear un nuevo servicio, luego use un BehaviorSubject o EventEmitter

 export Class myService { #all the stuff that must exist myString: string[] = []; contactChange : BehaviorSubject = new BehaviorSubject(this.myString); getContacts(newContacts) { // get your data from a webservices & when you done simply next the value this.contactChange.next(newContacts); } } 

Cuando lo haga, cada componente que use su servicio como proveedor tendrá conocimiento del cambio. Simplemente suscríbase al resultado como lo hace con eventEmitter;)

 export Class myComp { #all the stuff that exists like @Component + constructor using (private myService: myService) this.myService.contactChange.subscribe((contacts) => { this.contactList += contacts; //run everytime next is called } } 

Implementamos una directiva observable ngModelChange que envía todos los cambios del modelo a través de un emisor de eventos que instancia en su propio componente. Simplemente tiene que vincular su emisor de eventos a la directiva.

Ver: https://github.com/atomicbits/angular2-modelchangeobservable

En html, enlace su emisor de evento (countryChanged en este ejemplo):

  

En su componente de mecanografía, realice algunas operaciones de sincronización en el EventEmitter:

 import ... import {ModelChangeObservable} from './model-change-observable.directive' @Component({ selector: 'my-component', directives: [ModelChangeObservable], providers: [], templateUrl: 'my-component.html' }) export class MyComponent { @Input() country: Country selectedCountries:Country[] countries:Country[] = [] countryChanged:EventEmitter = new EventEmitter() constructor() { this.countryChanged .filter((text:string) => text.length > 2) .debounceTime(300) .subscribe((countryName:string) => { let query = new RegExp(countryName, 'ig') this.selectedCountries = this.countries.filter((country:Country) => { return query.test(country.name) }) }) } } 

Esta es mi versión:

 export interface IEventListenr extends OnDestroy{ ngOnDestroy(): void } @Injectable() export class EventManagerService { private listeners = {}; private subject = new EventEmitter(); private eventObserver = this.subject.asObservable(); constructor() { this.eventObserver.subscribe(({name,args})=>{ if(this.listeners[name]) { for(let listener of this.listeners[name]) { listener.callback(args); } } }) } public registerEvent(eventName:string,eventListener:IEventListenr,callback:any) { if(!this.listeners[eventName]) this.listeners[eventName] = []; let eventExist = false; for(let listener of this.listeners[eventName]) { if(listener.eventListener.constructor.name==eventListener.constructor.name) { eventExist = true; break; } } if(!eventExist) { this.listeners[eventName].push({eventListener,callback}); } } public unregisterEvent(eventName:string,eventListener:IEventListenr) { if(this.listeners[eventName]) { for(let i = 0; i 

utilizar:

 export class  implements IEventListener{ constructor(private eventManager: EventManagerService) { this.eventManager.registerEvent('EVENT_NAME',this,(args:any)=>{ .... }) } ngOnDestroy(): void { this.eventManager.unregisterEvent('closeModal',this) } 

}

emitir:

  this.eventManager.emit("EVENT_NAME"); 

Eventos de servicio: los componentes pueden suscribirse a eventos de servicio. Por ejemplo, dos componentes hermanos pueden suscribirse al mismo evento de servicio y responder modificando sus respectivos modelos. Más sobre esto a continuación.

Pero asegúrese de anular la suscripción a eso en la destrucción del componente principal.