Açısal Küresel Olaylar


224

Açısal'da $scope.emit()veya bu konuda eşdeğeri yok $scope.broadcast()mu?

Ben EventEmitterişlevselliği biliyorum , ama anladığım kadarıyla bu sadece üst HTML öğesine bir olay yayar.

Fx arasında iletişim kurmam gerekirse ne olur? kardeşler veya DOM kökündeki bir bileşen ve birkaç seviye derin iç içe bir öğe arasında?


2
Dom'daki herhangi bir noktadan erişilebilen bir iletişim bileşeni oluşturmayla ilgili benzer bir sorum vardı: stackoverflow.com/questions/34572539/… Temel olarak, bir çözüm bir hizmete bir olay yayıcı koymaktır
brando

1
İşte RXJS kullanarak böyle bir hizmet benim uygulama abonelik üzerine nnci son değerleri almak sağlar. stackoverflow.com/questions/46027693/…
Codewarrior

Yanıtlar:


385

AngularJS'ye eşdeğer $scope.emit()veya $scope.broadcast()AngularJS den eşdeğer yoktur . Bir bileşenin içindeki EventEmitter yaklaşır, ancak belirttiğiniz gibi, yalnızca hemen üst bileşene bir olay gönderir.

Angular'da aşağıda açıklamaya çalışacağım başka alternatifler de var.

@Input () bağlamaları, uygulama modelinin yönlendirilmiş bir nesne grafiğine (kökten yapraklara) bağlanmasına izin verir. Bir bileşenin değişiklik algılayıcı stratejisinin varsayılan davranışı, bağlı herhangi bir bileşendeki tüm bağlamalar için tüm değişiklikleri bir uygulama modeline yaymaktır.

Yanda: İki tür model vardır: Modelleri ve Uygulama Modellerini Görüntüleyin. Bir uygulama modeli @Input () bağlamaları ile bağlanır. Görünüm modeli, bileşenin şablonunda bağlı olan yalnızca bir bileşen özelliğidir (@Input () ile dekore edilmez).

Sorularınızı cevaplamak için:

Kardeş bileşenler arasında iletişim kurmam gerekirse ne olur?

  1. Paylaşılan Uygulama Modeli : Kardeşler paylaşılan bir uygulama modeli aracılığıyla iletişim kurabilirler (tıpkı açısal 1 gibi). Örneğin, bir kardeş bir modelde değişiklik yaptığında, aynı modele bağlanan diğer kardeş otomatik olarak güncellenir.

  2. Bileşen Olayları : Alt bileşenler @Output () bağlamaları kullanarak üst bileşene olay gönderebilir. Üst bileşen olayı işleyebilir ve uygulama modelini veya kendi görünüm modelini değiştirebilir. Uygulama Modelindeki değişiklikler, aynı modele doğrudan veya dolaylı olarak bağlanan tüm bileşenlere otomatik olarak yayılır.

  3. Servis Olayları : Bileşenler servis olaylarına abone olabilir. Örneğin, iki kardeş bileşen aynı hizmet etkinliğine abone olabilir ve ilgili modellerini değiştirerek yanıt verebilir. Bu konuda daha fazlası aşağıda.

Bir Kök bileşeni ile birkaç seviye derinliğe yerleştirilmiş bir bileşen arasında nasıl iletişim kurabilirim?

  1. Paylaşılan Uygulama Modeli : Uygulama modeli, @Input () bağlamaları yoluyla Kök bileşeninden derin yuvalanmış alt bileşenlere geçirilebilir. Herhangi bir bileşenden bir modelde yapılan değişiklikler otomatik olarak aynı modeli paylaşan tüm bileşenlere yayılır.
  2. Hizmet Olayları : Ayrıca EventEmitter'ı, herhangi bir bileşenin hizmeti enjekte etmesine ve etkinliğe abone olmasına izin veren paylaşılan bir hizmete taşıyabilirsiniz. Bu şekilde, bir Kök bileşen bir hizmet yöntemini çağırabilir (tipik olarak modeli değiştirir) ve bu da bir olay yayar. Birkaç kat aşağı, aynı zamanda hizmeti enjekte eden ve aynı etkinliğe abone olan bir büyük çocuk bileşeni, bunu kaldırabilir. Paylaşılan bir Uygulama Modelini değiştiren herhangi bir olay işleyicisi otomatik olarak ona bağımlı olan tüm bileşenlere yayılır. Bu muhtemelen $scope.broadcast()Açısal 1'den en yakın eşdeğerdir . Bir sonraki bölümde bu fikir daha ayrıntılı olarak açıklanmaktadır.

Değişiklikleri Yaymak için Hizmet Olaylarını kullanan Gözlemlenebilir Hizmet Örneği

Aşağıda, değişiklikleri yaymak için hizmet olaylarını kullanan gözlenebilir bir hizmet örneği verilmiştir. Bir TodoItem eklendiğinde, hizmet bileşen abonelerini bildiren bir olay yayar.

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

Bir kök bileşenin etkinliğe nasıl abone olacağı aşağıda açıklanmıştır:

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

Birkaç seviye derinliğe yerleştirilmiş bir alt bileşen, etkinliğe aynı şekilde abone olur:

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

Etkinliği tetiklemek için hizmeti çağıran bileşen (bileşen ağacında herhangi bir yerde bulunabilir):

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

Referans: Açısal Olarak Değişiklik Algılama


27
Ben bir gözlemlenebilir veya EventEmitter için şimdi birkaç mesajda takip $ gördüm - örneğin itemAdded$,. Bu bir RxJS sözleşmesi falan mı? Bu nereden geliyor?
Mark Rajcok

1
Güzel cevap. "Uygulama Modelindeki değişiklikler otomatik olarak aynı modele doğrudan veya dolaylı olarak bağlanan tüm bileşenlere yayılır." Bu şekilde işe yaramadığına dair bir önsezim var (ama emin değilim). Savkin'in diğer blog yazısıstreet , uygulama modelinin özelliğini değiştiren bir bileşene örnek verir , ancak Angular 2 kimlik / referans ile değişiklik algılamayı uyguladığından onChanges, uygulama modeli referansı değişmediği için ( değişiklik yapılmaz) çağrılmaz ( çağrılmaz) devamı ...)
Mark Rajcok

10
Yanıtınızı hizmetteki EventEmitter yerine bir Gözlemlenebilir kullanmak üzere güncellemek isteyebilirsiniz. Bkz. Stackoverflow.com/a/35568924/215945 ve stackoverflow.com/questions/36076700
Mark Rajcok

2
Evet, $ eki, Cycle.js tarafından popüler hale getirilen bir RxJS sözleşmesidir. cycle.js.org/…
jody tate

4
Bir etkinliğe manuel olarak abone olmamanız gerekir. Son sürümde gözlemlenebilir olmayabilir! Şuna
NetProvoke

49

Aşağıdaki kod, olayları işlemek için paylaşılan bir hizmet kullanarak Açısal 2'de $ scope.emit () veya $ scope.broadcast () yerine bir örnek olarak .

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

Örnek kullanım:

Yayın yapmak:

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

dinleyici:

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

Birden fazla argümanı destekleyebilir:

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

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

Bu ne yapar? statik get parametreleri () {return [new Inject (EventsService)]; }
Beanwah

Bu örnekte Ionic 2 Framework kullanıyorum. Statik parametreler yöntemi, yapıcı yöntemi çağrıldığında çağrılır ve yapıcıya bağımlılıkları sağlamak için kullanılır. Burada açıklama stackoverflow.com/questions/35919593/…
jim.taylor.1974

1
Güzel yapılmış. Basit ve sadece bir kerelik değil, tüm uygulama için kolayca uyarlanabilir bir bildirim sistemi sağlar.
Mike M

Joker karakter desteği ile benzer bir hizmet oluşturdum. Umarım yardımcı olur. github.com/govorov/ng-radio
Stanislav E. Govorov

2
Harika, kullanılmış ama artık ilgileniyorsa bir off işlevi ekledi:off(name, listener) { this.listeners[name] = this.listeners[name].filter(x => x != listener); }
LVDM

16

Bir rxjs saran bir mesaj servisi kullanıyorum Subject (TypeScript)

Plunker örneği: Mesaj Servisi

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

Bileşenler olaylara abone olabilir ve yayın yapabilir (gönderen):

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

(alıcı)

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

subscribeYöntem MessageServicebir rxjs getiri Subscriptionşöyle aboneliği iptal edilebilir nesne:

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

Ayrıca bu yanıta bakın: https://stackoverflow.com/a/36782616/1861779

Plunker örneği: Mesaj Servisi


2
çok değerli. Cevap için teşekkürler. Bu yolla iki farklı modülde iki bileşenle iletişim kuramayacağınızı öğrendim . Hedefe ulaşmak için orada sağlayıcıları ekleyerek app.module düzeyinde MessageService kaydettirmek zorunda kaldım. Herhangi bir şekilde bu gerçekten harika bir yol.
Rukshan Dangalla

bunların hepsi güncel değil. özellikle herhangi bir kaynağı başarıyla yüklemeyen dalgıç. hepsi 500 hata kodudur.
tatsu

AnladımProperty 'filter' does not exist on type 'Subject<EventMessage>'.
Drew

@Drew, RxJS'nin yeni sürümlerinde kullanılır this.handler.pipe(filter(...)). Kiralanabilir operatörlere bakın .
t.888

1
@ t.888 teşekkürler, anladım. Güncellenmiş abonelik işlevi benziyorreturn this.handler.pipe( filter(message => message.type === type), map(message => message.payload) ).subscribe(callback);
Drew

12

KullanmayınHizmet iletişiminiz için EventEmitter'ı .

Gözlenebilir türlerden birini kullanmalısınız. Şahsen BehaviorSubject'i seviyorum.

Basit örnek:

İlk durumu geçebilirsin, işte null geçiyorum

konu = yeni BehaviorSubject (null);

Konuyu güncellemek istediğinizde

subject.next (myObject)

Herhangi bir servis veya bileşenden gözlemleyin ve yeni güncellemeler aldığında harekete geçin.

subject.subscribe (this.YOURMETHOD);

İşte daha fazla bilgi. .


1
bu tasarım kararının nedenlerini açıklayabilir misiniz?
mtraut

@mtraut bu bağlantının da kapsamlı bir açıklaması var.
Danial Kalbasi


İhtiyacım olan şey. Güzel ve basit :)
Düşük


2

En sevdiğim yol, tüm alt bileşenimi kontrol etmek için hizmetimde davranış konusu veya olay yayıcı (neredeyse aynı) kullanmak.

Açısal cli kullanarak, yeni bir hizmet oluşturmak için çalıştırma ve ardından bir BehaviorSubject veya EventEmitter kullanın

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

Bunu yaptığınızda, hizmetinizi sağlayıcı olarak kullanan her bileşen bu değişikliğin farkında olacaktır. EventEmitter ile yaptığınız gibi sonuca abone olmanız yeterlidir;)

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

Burada bir pub-sub örneği oluşturdum:

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

Buradaki fikir, bir gözlemci ve Gözlemlenebilir kabloları özel olayları yayınlamak ve bunlara abone olmak için genel bir çözüm olarak bağlamak için RxJ'lerin Konularını kullanmaktır. Örneğimde demo amaçlı bir müşteri nesnesi kullanıyorum

this.pubSubService.Stream.emit(customer);

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

İşte canlı bir demo da: http://www.syntaxsuccess.com/angular-2-samples/#/demo/pub-sub


1

Bu benim versiyonum:

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

kullanın:

export class <YOURCOMPONENT> implements IEventListener{

  constructor(private eventManager: EventManagerService) {


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


  }

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

}

yayarlar:

 this.eventManager.emit("EVENT_NAME");

0

Tüm model değişikliklerini kendi bileşeninizde başlattığınız bir olay gönderici aracılığıyla gönderen bir ngModelChange gözlemlenebilir yönergesi uyguladık. Sadece olay göndericinizi direktife bağlamanız yeterlidir.

Görmek: Https://github.com/atomicbits/angular2-modelchangeobservable

Html'de, etkinlik yayıcınızı bağlayın (country Bu örnekte değiştirildi):

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

Yazı tipi bileşeninizde, EventEmitter üzerinde bazı zaman uyumsuz işlemler yapın:

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

Servis Olayları: Bileşenler servis olaylarına abone olabilir. Örneğin, iki kardeş bileşen aynı hizmet etkinliğine abone olabilir ve ilgili modellerini değiştirerek yanıt verebilir. Bu konuda daha fazlası aşağıda.

Ancak ana bileşenin yok edilmesiyle ilgili aboneliğinizi iptal ettiğinizden emin olun.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.