Eventi globali in angolare


224

Non esiste un equivalente $scope.emit()o $scope.broadcast()in angolare?

Conosco la EventEmitterfunzionalità, ma per quanto ne so, emetterà un evento sull'elemento HTML genitore.

E se avessi bisogno di comunicare tra FX. fratelli o tra un componente nella radice del DOM e un elemento nidificato a diversi livelli di profondità?


2
Avevo una domanda simile relativa alla creazione di un componente di dialogo a cui era possibile accedere da qualsiasi punto della dom: stackoverflow.com/questions/34572539/… Fondamentalmente, una soluzione è mettere un emettitore di eventi in un servizio
brando

1
Ecco la mia implementazione di un tale servizio utilizzando RXJS che consente di ottenere l'ennesimo valore ultimo al momento dell'abbonamento. stackoverflow.com/questions/46027693/...
CodeWarrior

Risposte:


385

Non esiste un equivalente da $scope.emit()o $scope.broadcast()verso AngularJS. EventEmitter all'interno di un componente si avvicina, ma come hai detto, emetterà un evento solo al componente genitore immediato.

In angolare, ci sono altre alternative che proverò a spiegare di seguito.

I bind @Input () consentono di collegare il modello di applicazione in un grafico a oggetti diretto (da radice a foglie). Il comportamento predefinito della strategia del rilevatore di modifiche di un componente è di propagare tutte le modifiche a un modello di applicazione per tutti i binding da qualsiasi componente collegato.

A parte: ci sono due tipi di modelli: Visualizza modelli e Modelli di applicazione. Un modello di applicazione è collegato tramite collegamenti @Input (). Un modello di vista è solo una proprietà del componente (non decorata con @Input ()) che è associata al modello del componente.

Per rispondere alle tue domande:

Cosa succede se devo comunicare tra i componenti dei fratelli?

  1. Modello di applicazione condiviso : i fratelli possono comunicare attraverso un modello di applicazione condiviso (proprio come l'angolo 1). Ad esempio, quando un fratello effettua una modifica a un modello, l'altro fratello che ha i collegamenti allo stesso modello viene automaticamente aggiornato.

  2. Eventi componente : i componenti figlio possono emettere un evento al componente padre usando i collegamenti @Output (). Il componente padre può gestire l'evento e manipolare il modello dell'applicazione o il proprio modello di visualizzazione. Le modifiche al modello di applicazione vengono propagate automaticamente a tutti i componenti che si legano direttamente o indirettamente allo stesso modello.

  3. Eventi di servizio : i componenti possono iscriversi agli eventi di servizio. Ad esempio, due componenti di pari livello possono abbonarsi allo stesso evento di servizio e rispondere modificando i rispettivi modelli. Maggiori informazioni su questo sotto.

Come posso comunicare tra un componente Root e un componente nidificato a diversi livelli di profondità?

  1. Modello di applicazione condivisa : il modello di applicazione può essere passato dal componente Root ai componenti secondari profondamente nidificati tramite i collegamenti @Input (). Le modifiche a un modello da qualsiasi componente si propagheranno automaticamente a tutti i componenti che condividono lo stesso modello.
  2. Eventi di servizio : è anche possibile spostare EventEmitter su un servizio condiviso, che consente a qualsiasi componente di iniettare il servizio e iscriversi all'evento. In questo modo, un componente Root può chiamare un metodo di servizio (in genere mutando il modello), che a sua volta emette un evento. Diversi livelli in basso, un componente grand-child che ha anche iniettato il servizio e sottoscritto lo stesso evento, può gestirlo. Qualsiasi gestore di eventi che modifica un modello di applicazione condiviso, si propaga automaticamente a tutti i componenti che dipendono da esso. Questo è probabilmente l'equivalente più vicino a $scope.broadcast()da Angolare 1. La sezione successiva descrive questa idea in modo più dettagliato.

Esempio di un servizio osservabile che utilizza gli eventi di servizio per propagare le modifiche

Ecco un esempio di un servizio osservabile che utilizza eventi di servizio per propagare le modifiche. Quando viene aggiunto un TodoItem, il servizio emette un evento che notifica ai suoi abbonati componenti.

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

Ecco come un componente root si iscriverebbe all'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 figlio nidificato a più livelli si iscriverebbe all'evento allo stesso modo:

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

Ecco il componente che chiama il servizio per attivare l'evento (può risiedere ovunque nella struttura dei componenti):

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

Riferimento: rilevamento del cambiamento in angolare


27
Ho visto il trailing $ in alcuni post ora per un osservabile o EventEmitter - ad esempio itemAdded$. È una convenzione RxJS o qualcosa del genere? Da dove viene?
Mark Rajcok,

1
Bella risposta. Hai dichiarato: "Le modifiche al modello di app vengono propagate automaticamente a tutti i componenti che si legano direttamente o indirettamente allo stesso modello". Ho la sensazione che non funzioni così (ma non ne sono sicuro). L'altro post sul blog di Savkin fornisce un esempio di un componente che modifica la streetproprietà del modello di app, ma poiché Angular 2 implementa il rilevamento delle modifiche per identità / riferimento, non vengono propagate modifiche ( onChangesnon viene chiamato), poiché il riferimento del modello di app non è cambiato ( cont ...)
Mark Rajcok il

10
Potresti voler aggiornare la tua risposta per utilizzare un osservabile anziché un EventEmitter nel servizio. Vedere stackoverflow.com/a/35568924/215945 e stackoverflow.com/questions/36076700
Mark Rajcok

2
Sì, il suffisso $ è una convenzione RxJS resa popolare da Cycle.js. cycle.js.org/…
jody tate,

4
Non è necessario iscriversi manualmente a un eventemitter. Potrebbe non essere osservabile nella versione finale! Vedi questo: bennadel.com/blog/…
NetProvoke il

49

Il seguente codice come esempio di una sostituzione per $ scope.emit () o $ scope.broadcast () in Angular 2 utilizzando un servizio condiviso per gestire gli eventi.

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

Esempio di utilizzo:

Trasmissione:

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

Ascoltatore:

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

Può supportare più argomenti:

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

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

Cosa fa questo? static get parametri () {return [new Inject (EventsService)]; }
Beanwah,

In questo esempio sto usando Ionic 2 Framework. Il metodo dei parametri statici viene chiamato quando viene invocato il metodo del costruttore e viene utilizzato per fornire le dipendenze al costruttore. Spiegazione qui stackoverflow.com/questions/35919593/...~~V~~singular~~3rd
jim.taylor.1974

1
Ben fatto. Semplice e fornisce un sistema di notifica facilmente adattabile per l'intera app, non solo una tantum.
Mike M,

Ho appena creato un servizio simile con il supporto dei caratteri jolly. Spero che sia d'aiuto. github.com/govorov/ng-radio
Stanislav E. Govorov

2
Fantastico, l'ho usato ma ha aggiunto una funzione off se più è interessato: off(name, listener) { this.listeners[name] = this.listeners[name].filter(x => x != listener); }
LVDM il

16

Sto usando un servizio di messaggistica che include un rxjs Subject(TypeScript)

Esempio di Plunker: Servizio messaggi

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

I componenti possono iscriversi e trasmettere eventi (mittente):

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

(ricevitore)

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

Il subscribemetodo di MessageServicerestituisce un Subscriptionoggetto rxjs , che può essere annullato in questo modo:

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

Vedi anche questa risposta: https://stackoverflow.com/a/36782616/1861779

Esempio di Plunker: Servizio messaggi


2
molto apprezzabile. Grazie per la risposta. Ho appena scoperto che in questo modo non è possibile comunicare con due componenti in due moduli diversi . Per raggiungere l'obiettivo ho dovuto registrare MessageService a livello di app.module aggiungendo fornitori lì. In ogni caso questo è un modo davvero interessante.
Rukshan Dangalla,

tutto questo è tristemente obsoleto. specialmente il plunker che non carica correttamente alcuna risorsa. sono tutti 500 codici di errore.
Tatsu,

RicevoProperty 'filter' does not exist on type 'Subject<EventMessage>'.
disegnato il

@Drew, sulle nuove versioni di RxJS this.handler.pipe(filter(...)). Vedi operatori disponibili .
t.888

1
@ t.888 grazie, l'ho capito. La funzione di iscrizione aggiornata è simile:return this.handler.pipe( filter(message => message.type === type), map(message => message.payload) ).subscribe(callback);
Disegnata il

12

NON utilizzare EventEmitter per le comunicazioni di servizio.

È necessario utilizzare uno dei tipi osservabili. Personalmente mi piace BehaviorSubject.

Esempio semplice:

Puoi passare lo stato iniziale, qui passo null

let subject = new BehaviorSubject (null);

Quando vuoi aggiornare l'argomento

subject.next (myObject)

Osservare da qualsiasi servizio o componente e agire quando riceve nuovi aggiornamenti.

subject.subscribe (this.YOURMETHOD);

Ecco maggiori informazioni. .


1
potresti approfondire le ragioni di questa decisione progettuale?
Mtraut,

@mtraut quel link ha anche una spiegazione completa.
Danial Kalbasi,

per una spiegazione più dettagliata su come usare BehaviourSubject leggi questo articolo blog.cloudboost.io/…
rafalkasa,

Proprio quello di cui avevo bisogno. Bello e semplice :)
Basso


2

Il mio modo preferito di fare è usare il soggetto del comportamento o l'emettitore di eventi (quasi lo stesso) nel mio servizio per controllare tutto il mio sottocomponente.

Usando angular cli, esegui ng gs per creare un nuovo servizio, quindi usa 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);
   }
}

Quando lo fai, ogni componente che utilizza il tuo servizio come fornitore sarà a conoscenza della modifica. Iscriviti semplicemente al risultato come fai 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

Ho creato un esempio di pub-sub qui:

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

L'idea è di utilizzare i soggetti RxJ per collegare un osservatore e osservabili come una soluzione generica per l'emissione e la sottoscrizione di eventi personalizzati. Nel mio esempio utilizzo un oggetto cliente per scopi dimostrativi

this.pubSubService.Stream.emit(customer);

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

Ecco anche una demo live: http://www.syntaxsuccess.com/angular-2-samples/#/demo/pub-sub


1

Questa è la mia versione:

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

uso:

export class <YOURCOMPONENT> implements IEventListener{

  constructor(private eventManager: EventManagerService) {


    this.eventManager.registerEvent('EVENT_NAME',this,(args:any)=>{
       ....
    })


  }

  ngOnDestroy(): void {
    this.eventManager.unregisterEvent('closeModal',this)
  }

}

emettere:

 this.eventManager.emit("EVENT_NAME");

0

Abbiamo implementato una direttiva osservabile ngModelChange che invia tutte le modifiche al modello tramite un emettitore di eventi che viene istanziato nel proprio componente. Devi semplicemente vincolare il tuo emettitore di eventi alla direttiva.

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

In html, associa l'emettitore di eventi (countryChanged in questo esempio):

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

Nel tuo componente dattiloscritto, esegui alcune operazioni asincrone su 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

Eventi di servizio: i componenti possono iscriversi agli eventi di servizio. Ad esempio, due componenti di pari livello possono abbonarsi allo stesso evento di servizio e rispondere modificando i rispettivi modelli. Maggiori informazioni su questo sotto.

Ma assicurati di annullare l'iscrizione a distruggere il componente genitore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.