Eventos globales en angular


224

¿No hay un equivalente a $scope.emit(), o $scope.broadcast()en angular?

Conozco la EventEmitterfuncionalidad, pero por lo que entiendo, solo emitirá un evento para el elemento HTML principal.

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


2
Tenía una pregunta similar relacionada con la creación de un componente de diálogo al que se podía acceder desde cualquier punto de dom: stackoverflow.com/questions/34572539/... Básicamente, una solución es poner un emisor de eventos en un servicio
brando

1
Aquí está mi implementación de dicho servicio usando RXJS que permite obtener los últimos enésimos valores al momento de la suscripción. stackoverflow.com/questions/46027693/…
Codewarrior

Respuestas:


385

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

En Angular, hay otras alternativas que intentaré 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 todos los enlaces desde cualquier componente conectado.

Aparte: hay dos tipos de modelos: Ver modelos y Modelos de aplicación. Un modelo de aplicación está conectado a través de enlaces @Input (). Un modelo de vista es solo una propiedad de componente (no decorada con @Input ()) que está vinculada en la plantilla del componente.

Para responder tu pregunta:

¿Qué sucede 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 compartida (al igual que 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. Eventos de componentes : los componentes secundarios pueden emitir un evento al componente principal mediante 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 se unen directa o indirectamente 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 raíz y un componente anidado a varios niveles de profundidad?

  1. Modelo de aplicación compartida : el modelo de aplicación se puede pasar del componente raíz a subcomponentes profundamente anidados mediante enlaces @Input (). Los cambios en un modelo desde cualquier componente se propagarán automáticamente a todos los componentes que compartan el mismo modelo.
  2. Eventos de servicio : también puede mover EventEmitter a un servicio compartido, que permite que cualquier componente inyecte el servicio y se suscriba al evento. De esa manera, un componente 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 nieto que también ha inyectado el servicio y 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 dependen de él. Este es probablemente el equivalente más cercano a $scope.broadcast()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 TodoItem, el servicio emite un evento que notifica a sus suscriptores componentes.

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

Así es como 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 hijo anidado en varios niveles profundos 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 activar el evento (puede residir en cualquier parte del árbol de componentes):

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

Referencia: Detección de cambios en angular


27
He visto el arrastre $ en unos pocos puestos ahora para un observable o EventEmitter - por ejemplo, itemAdded$. ¿Es una convención RxJS o algo así? ¿De donde viene esto?
Mark Rajcok

1
Buena respuesta. Usted declaró: "Los cambios en el modelo de la aplicación se propagan automáticamente a todos los componentes que se unen directa o indirectamente al mismo modelo". Tengo el presentimiento de que no funciona de esta manera (pero no estoy seguro). La otra publicación de blog de Savkin da un ejemplo de un componente que cambia la streetpropiedad del modelo de la aplicación, pero dado que Angular 2 implementa la detección de cambios por identidad / referencia, no se propagan cambios ( onChangesno se llama), porque la referencia del modelo de la aplicación no ha cambiado ( cont ...)
Mark Rajcok

10
Es posible que desee actualizar su respuesta para usar un Observable en lugar de un EventEmitter en el servicio. Consulte stackoverflow.com/a/35568924/215945 y stackoverflow.com/questions/36076700
Mark Rajcok el

2
Sí, el sufijo $ es una convención RxJS popularizada por Cycle.js. cycle.js.org/…
jody tate

44
No debe suscribirse manualmente a un emisor de eventos. ¡Puede que no sea observable en la versión final! Vea esto: bennadel.com/blog/…
NetProvoke

49

El siguiente código como ejemplo de un reemplazo para $ scope.emit () o $ scope.broadcast () en Angular 2 usando 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);
    }

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

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

Ejemplo de uso:

Transmitir:

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

¿Qué hace esto? parámetros de obtención estáticos () {return [new Inject (EventsService)]; }
Beanwah

En este ejemplo, estoy usando Ionic 2 Framework. El método de parámetros estáticos se llama cuando se invoca el método del constructor y se utiliza para suministrar las dependencias al constructor. Explicación aquí stackoverflow.com/questions/35919593/…
jim.taylor.1974

1
Bien hecho. Simple y proporciona un sistema de notificación fácilmente adaptable para toda la aplicación, no solo de una sola vez.
Mike M

Acabo de crear un servicio similar con soporte para comodines. Espero eso ayude. github.com/govorov/ng-radio
Stanislav E. Govorov

2
Impresionante, lo usé pero agregó una función de apagado si ya está interesado: off(name, listener) { this.listeners[name] = this.listeners[name].filter(x => x != listener); }
LVDM

16

Estoy usando un servicio de mensajes que envuelve un rxjs Subject (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<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);
  }
}

Los componentes pueden suscribirse y transmitir eventos (remitente):

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 subscribemétodo de MessageServicedevuelve un Subscriptionobjeto rxjs , que se puede cancelar de la siguiente manera:

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


2
muy valioso. Gracias por la respuesta. Me acabo de enterar que no puede comunicarse con dos componentes en dos módulos diferentes de esta manera. Para lograr el objetivo, tuve que registrar MessageService en el nivel app.module agregando proveedores allí. De cualquier manera, esta es una forma realmente genial.
Rukshan Dangalla

Todo esto es lamentablemente desactualizado. especialmente el plunker que no carga ningún recurso con éxito. Todos son 500 códigos de error.
tatsu

Me saleProperty 'filter' does not exist on type 'Subject<EventMessage>'.
Drew

@Drew, en versiones más nuevas del uso de RxJS this.handler.pipe(filter(...)). Ver operadores alquilables .
t.888

1
@ t.888 gracias, lo descubrí. La función de suscripción actualizada se ve asíreturn this.handler.pipe( filter(message => message.type === type), map(message => message.payload) ).subscribe(callback);
Drew, el

12

NO use EventEmitter para la comunicación de su servicio.

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

Ejemplo simple:

Puedes pasar el estado inicial, aquí paso nulo

let subject = new BehaviorSubject (nulo);

Cuando quieres actualizar el tema

subject.next (myObject)

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

sujeto.suscribirse (este.YOURMETHOD);

Aquí hay más información. .


1
¿Podría explicar los motivos de esta decisión de diseño?
mtraut

@mtraut ese enlace también tiene una explicación completa.
Danial Kalbasi

Para obtener una explicación más detallada sobre cómo usar BehaviourSubject, lea este artículo blog.cloudboost.io/…
rafalkasa

Eaxctly lo que necesitaba. Nice and simple :)
Bajo


2

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, ejecute ng gs para crear un nuevo servicio y luego use un BehaviorSubject o EventEmitter

export Class myService {
#all the stuff that must exist

myString: string[] = [];
contactChange : BehaviorSubject<string[]> = 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, todos los componentes que utilicen su servicio como proveedor serán conscientes 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
  }
}

1

He creado una muestra de pub-sub aquí:

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

La idea es utilizar los sujetos RxJs para conectar un observador y observables como una solución genérica para emitir y suscribirse a eventos personalizados. En mi muestra utilizo un objeto de cliente con fines de demostración

this.pubSubService.Stream.emit(customer);

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

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


1

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<this.listeners[eventName].length;i++)
            {

                if(this.listeners[eventName][i].eventListener.constructor.name==eventListener.constructor.name)
                {
                    this.listeners[eventName].splice(i, 1);
                    break;
                }
            }
        }


    }


    emit(name:string,...args:any[])
    {
        this.subject.next({name,args});
    }
}

utilizar:

export class <YOURCOMPONENT> 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");

0

Implementamos una directiva observable ngModelChange que envía todos los cambios del modelo a través de un emisor de eventos que usted 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, vincule su emisor de eventos (countryChanged en este ejemplo):

<input [(ngModel)]="country.name"
       [modelChangeObservable]="countryChanged" 
       placeholder="Country"
       name="country" id="country"></input>

En su componente de mecanografiado, realice algunas operaciones asíncronas en 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[] = <Country[]>[]
    countryChanged:EventEmitter<string> = new EventEmitter<string>()


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

0

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 darse de baja para destruir el componente principal.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.