Acara Global di Angular


224

Apakah tidak ada yang setara dengan $scope.emit()atau$scope.broadcast() di Angular?

Saya tahu EventEmitterfungsionalitasnya, tetapi sejauh yang saya mengerti itu hanya akan memancarkan suatu peristiwa ke elemen HTML induk.

Bagaimana jika saya perlu berkomunikasi antara fx. saudara atau antara komponen di root DOM dan elemen bersarang beberapa tingkat dalam?


2
Saya punya pertanyaan serupa terkait dengan membuat komponen dialog yang dapat diakses dari titik mana saja di dom: stackoverflow.com/questions/34572539/... Pada dasarnya, salah satu solusinya adalah dengan menempatkan pemancar acara dalam layanan
brando

1
Berikut ini adalah implementasi layanan semacam itu menggunakan RXJS yang memungkinkan untuk mendapatkan nilai terakhir ke-n saat berlangganan. stackoverflow.com/questions/46027693/…
Codewarrior

Jawaban:


385

Tidak ada yang setara dengan $scope.emit()atau$scope.broadcast() dari AngularJS. EventEmitter di dalam komponen sudah dekat, tetapi seperti yang Anda sebutkan, itu hanya akan memancarkan sebuah peristiwa ke komponen induk langsung.

Di Angular, ada alternatif lain yang akan saya coba jelaskan di bawah ini.

@Input () binding memungkinkan model aplikasi untuk dihubungkan dalam grafik objek yang diarahkan (root to leaves). Perilaku default dari strategi detektor perubahan komponen adalah untuk menyebarkan semua perubahan ke model aplikasi untuk semua ikatan dari komponen yang terhubung.

Selain: Ada dua jenis model: Lihat Model dan Model Aplikasi. Model aplikasi terhubung melalui @Input () binding. Model tampilan hanyalah properti komponen (tidak didekorasi dengan @Input ()) yang terikat dalam templat komponen.

Untuk menjawab pertanyaan Anda:

Bagaimana jika saya perlu berkomunikasi antara komponen saudara?

  1. Model Aplikasi Bersama : Saudara dapat berkomunikasi melalui model aplikasi bersama (seperti sudut 1). Misalnya, ketika satu saudara kandung membuat perubahan ke model, saudara kandung lainnya yang memiliki ikatan dengan model yang sama diperbarui secara otomatis.

  2. Component Events : Komponen child dapat memancarkan sebuah event ke komponen induk menggunakan binding @Output (). Komponen induk dapat menangani acara, dan memanipulasi model aplikasi atau model tampilan itu sendiri. Perubahan pada Model Aplikasi secara otomatis disebarkan ke semua komponen yang secara langsung atau tidak langsung mengikat ke model yang sama.

  3. Acara Layanan : Komponen dapat berlangganan acara layanan. Misalnya, dua komponen saudara dapat berlangganan ke acara layanan yang sama dan merespons dengan memodifikasi model masing-masing. Lebih lanjut tentang ini di bawah ini.

Bagaimana saya bisa berkomunikasi antara komponen Root dan komponen yang bersarang dalam beberapa level?

  1. Model Aplikasi Bersama: Model aplikasi dapat diturunkan dari komponen Root ke sub-komponen bersarang melalui @Input () binding. Perubahan model dari komponen apa pun akan secara otomatis menyebar ke semua komponen yang memiliki model yang sama.
  2. Acara Layanan : Anda juga dapat memindahkan EventEmitter ke layanan bersama, yang memungkinkan komponen mana pun untuk menyuntikkan layanan dan berlangganan ke acara tersebut. Dengan begitu, komponen Root dapat memanggil metode layanan (biasanya bermutasi model), yang pada gilirannya memancarkan suatu peristiwa. Beberapa lapisan ke bawah, komponen cucu yang juga menyuntikkan layanan dan berlangganan ke acara yang sama, dapat menanganinya. Pengatur kejadian apa pun yang mengubah Model Aplikasi yang dibagikan, akan secara otomatis menyebar ke semua komponen yang bergantung padanya. Ini mungkin setara dengan terdekat $scope.broadcast()dari Angular 1. Bagian selanjutnya menjelaskan ide ini secara lebih rinci.

Contoh Layanan yang Dapat Diobservasi yang menggunakan Acara Layanan untuk Menyebarkan Perubahan

Berikut adalah contoh layanan yang dapat diamati yang menggunakan acara layanan untuk menyebarkan perubahan. Ketika TodoItem ditambahkan, layanan memancarkan acara yang memberitahukan pelanggan komponennya.

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

Berikut adalah bagaimana komponen root akan berlangganan acara:

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

Komponen anak yang bersarang beberapa level akan berlangganan acara dengan cara yang sama:

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

Berikut adalah komponen yang memanggil layanan untuk memicu acara (itu bisa berada di mana saja di pohon komponen):

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

Referensi: Ubah Deteksi dalam Angular


27
Saya telah melihat jejak $ di beberapa posting sekarang untuk diobservasi atau EventEmitter - misalnya itemAdded$,. Apakah itu konvensi RxJS atau sesuatu? Dari mana ini berasal?
Mark Rajcok

1
Jawaban bagus. Anda menyatakan, "Perubahan pada Model Aplikasi secara otomatis disebarkan ke semua komponen yang secara langsung atau tidak langsung mengikat ke model yang sama." Saya punya firasat bahwa itu tidak cukup bekerja dengan cara ini (tapi saya tidak yakin). Posting blog Savkin yang lain memberikan contoh komponen yang mengubah streetproperti model aplikasi, tetapi karena Angular 2 mengimplementasikan deteksi perubahan dengan identitas / referensi, tidak ada perubahan yang diperbanyak ( onChangestidak disebut), karena referensi model aplikasi tidak berubah ( lanjutan ...)
Mark Rajcok

10
Anda mungkin ingin memperbarui jawaban Anda untuk menggunakan Observable daripada EventEmitter dalam layanan. Lihat stackoverflow.com/a/35568924/215945 dan stackoverflow.com/questions/36076700
Mark Rajcok

2
Ya, $ suffixed adalah konvensi RxJS yang dipopulerkan oleh Cycle.js. cycle.js.org/…
jody tate

4
Anda tidak boleh berlangganan secara manual ke pengirim acara. Ini mungkin tidak dapat diamati dalam rilis final! Lihat ini: bennadel.com/blog/…
NetProvoke

49

Kode berikut sebagai contoh pengganti $ scope.emit () atau $ scope.broadcast () di Angular 2 menggunakan layanan bersama untuk menangani acara.

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

Contoh penggunaan:

Siaran:

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

Pendengar:

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

Itu dapat mendukung banyak argumen:

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

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

Apa fungsinya? parameter get statis () {return [Suntikkan baru (EventsService)]; }
Beanwah

Dalam contoh ini saya menggunakan Ionic 2 Framework. Metode parameter statis dipanggil ketika metode konstruktor dipanggil dan digunakan untuk memasok dependensi ke konstruktor. Penjelasan di sini stackoverflow.com/questions/35919593/...
jim.taylor.1974

1
Bagus sekali. Sederhana dan menyediakan sistem pemberitahuan yang mudah disesuaikan untuk seluruh aplikasi, bukan hanya satu kali.
Mike M

Saya baru saja membuat layanan serupa dengan dukungan wildcard. Semoga ini bisa membantu. github.com/govorov/ng-radio
Stanislav E. Govorov

2
Luar biasa, menggunakannya tetapi menambahkan fungsi tidak aktif jika lagi tertarik: off(name, listener) { this.listeners[name] = this.listeners[name].filter(x => x != listener); }
LVDM

16

Saya menggunakan layanan pesan yang membungkus rxjs Subject(TypeScript)

Contoh Plunker: Layanan Pesan

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

Komponen dapat berlangganan dan menyiarkan acara (pengirim):

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

(penerima)

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

The subscribemetode MessageServicepengembalian suatu rxjs Subscriptionobjek, yang dapat berhenti berlangganan dari seperti:

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

Lihat juga jawaban ini: https://stackoverflow.com/a/36782616/1861779

Contoh Plunker: Layanan Pesan


2
sangat berharga. Terima kasih atas jawabannya. Saya baru tahu Anda tidak dapat berkomunikasi dengan dua komponen dalam dua modul yang berbeda menggunakan cara ini. Untuk mencapai tujuan saya harus mendaftarkan MessageService di tingkat app.module dengan menambahkan penyedia di sana. Bagaimanapun cara ini adalah cara yang sangat keren.
Rukshan Dangalla

ini semua sudah ketinggalan zaman. terutama plunker yang tidak berhasil memuat sumber apa pun. semuanya 500 kode kesalahan.
tatsu

Saya dapatProperty 'filter' does not exist on type 'Subject<EventMessage>'.
Drew

@Rew, pada versi yang lebih baru dari penggunaan RxJS this.handler.pipe(filter(...)). Lihat operator yang memungkinkan .
t.888

1
@ t.888 terima kasih, saya sudah menemukan jawabannya. Fungsi berlangganan yang diperbarui terlihat sepertireturn this.handler.pipe( filter(message => message.type === type), map(message => message.payload) ).subscribe(callback);
Drew

12

JANGAN Gunakan EventEmitter untuk komunikasi layanan Anda.

Anda harus menggunakan salah satu dari tipe yang Dapat Diobservasi. Saya pribadi suka BehaviorSubject.

Contoh sederhana:

Anda dapat melewati kondisi awal, di sini saya melewati nol

biarkan subject = new BehaviorSubject (null);

Ketika Anda ingin memperbarui subjek

subject.next (myObject)

Amati dari setiap layanan atau komponen dan bertindaklah ketika mendapat pembaruan baru.

subject.subscribe (this.YOURMETHOD);

Berikut ini informasi lebih lanjut. .


1
Bisakah Anda menguraikan alasan untuk keputusan desain ini?
mtraut

@ traut tautan itu juga memiliki penjelasan yang komprehensif.
Danial Kalbasi

untuk penjelasan lebih rinci cara menggunakan BehaviourSubject, silakan baca artikel ini blog.cloudboost.io/…
rafalkasa

Tepatnya apa yang saya butuhkan. Bagus dan sederhana :)
Rendah


2

Cara favorit saya untuk melakukannya adalah dengan menggunakan subjek perilaku atau emitor acara (hampir sama) di layanan saya untuk mengontrol semua subkomponen saya.

Menggunakan angular cli, jalankan ng gs untuk membuat layanan baru kemudian gunakan BehaviorSubject atau 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);
   }
}

Ketika Anda melakukan itu, setiap komponen yang menggunakan layanan Anda sebagai penyedia akan menyadari perubahan tersebut. Cukup berlangganan ke hasil seperti yang Anda lakukan dengan 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

Saya telah membuat sampel pub-sub di sini:

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

Idenya adalah untuk menggunakan Subjek RxJs untuk memasang Pengamat dan dan Observable sebagai solusi umum untuk memancarkan dan berlangganan acara kustom. Dalam sampel saya, saya menggunakan objek pelanggan untuk tujuan demo

this.pubSubService.Stream.emit(customer);

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

Berikut ini adalah demo langsung: http://www.syntaxsuccess.com/angular-2-samples/#/demo/pub-sub


1

Ini versi saya:

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

menggunakan:

export class <YOURCOMPONENT> implements IEventListener{

  constructor(private eventManager: EventManagerService) {


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


  }

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

}

memancarkan:

 this.eventManager.emit("EVENT_NAME");

0

Kami menerapkan arable ngModelChange yang dapat diobservasi yang mengirimkan semua perubahan model melalui emitor acara yang Anda instantiate di komponen Anda sendiri. Anda hanya harus mengikat emitor acara Anda ke arahan.

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

Di html, ikat emitor acara Anda (countryChanged dalam contoh ini):

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

Di komponen naskah Anda, lakukan beberapa operasi async pada 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

Acara Layanan: Komponen dapat berlangganan acara layanan. Misalnya, dua komponen saudara dapat berlangganan ke acara layanan yang sama dan merespons dengan memodifikasi model masing-masing. Lebih lanjut tentang ini di bawah ini.

Tetapi pastikan untuk berhenti berlangganan pada yang menghancurkan komponen induk.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.