Глобальні події в Angular


224

Чи немає еквівалента $scope.emit()або $scope.broadcast()кутового?

Я знаю EventEmitterфункціональність, але, наскільки я розумію, це просто випромінить подія для батьківського елемента HTML.

Що робити, якщо мені потрібно зв’язатись між fx. братів і сестер або між компонентом у корені DOM та елементом, вкладеним у кілька рівнів глибиною?


2
У мене було подібне запитання, пов'язане зі створенням діалогового компонента, до якого можна отримати доступ з будь-якої точки дому: stackoverflow.com/questions/34572539/… В основному, одне рішення - поставити емітер події в службу
brando

1
Ось моя реалізація такої послуги з використанням RXJS, яка дозволяє отримати n-е останні значення при підписці. stackoverflow.com/questions/46027693 / ...
Codewarrior

Відповіді:


385

Не існує еквівалента $scope.emit()або $scope.broadcast()з AngularJS. EventEmitter всередині компонента наближається, але, як ви вже згадували, він посилатиме лише подію безпосередньо безпосередньому батьківському компоненту.

У Angular є інші альтернативи, які я спробую пояснити нижче.

Прив'язки @ Input () дозволяють підключати модель програми у графіці спрямованого об'єкта (корінь до листя). Типовою поведінкою стратегії детектора змін компонента є розповсюдження всіх змін у моделі програми для всіх прив’язок будь-якого підключеного компонента.

Убік: Є два типи моделей: Перегляд моделей та Моделів додатків. Модель програми підключається через прив'язки @Input (). Модель перегляду - це просто властивість компонента (не прикрашена @Input ()), яка пов'язана у шаблоні компонента.

Щоб відповісти на ваші запитання:

Що робити, якщо мені потрібно зв’язатися між компонентами братів і сестер?

  1. Модель спільної програми : Брати та сестри можуть спілкуватися через спільну модель програми (як і кутова 1). Наприклад, коли один брат побратимів вносить зміни до моделі, інший брат, який має прив’язки до тієї ж моделі, автоматично оновлюється.

  2. Події компонента : Дочірні компоненти можуть передавати подію батьківському компоненту, використовуючи прив'язки @Output (). Батьківський компонент може обробляти подію та маніпулювати моделлю програми або власною моделлю перегляду. Зміни до прикладної моделі автоматично поширюються на всі компоненти, які прямо чи опосередковано пов'язуються з тією ж моделлю.

  3. Сервісні події : Компоненти можуть підписатися на сервісні події. Наприклад, два компоненти братів можуть брати підписку на одну службову подію та відповідати, змінюючи відповідні моделі. Детальніше про це нижче.

Як я можу зв’язатись між компонентом Root та компонентом, вкладеним у кілька рівнів глибоко?

  1. Спільна модель програми : Модель програми може бути передана від компонента Root вниз до глибоко вкладених підкомпонентів через прив'язки @Input (). Зміни в моделі з будь-якого компонента автоматично поширюються на всі компоненти, які мають одну і ту ж модель.
  2. Службові події : Ви також можете перемістити EventEmitter до спільної служби, яка дозволяє будь-якому компоненту вводити послугу та підписуватися на подію. Таким чином, компонент Root може викликати метод обслуговування (як правило, мутує модель), який, в свою чергу, випромінює подію. Кілька шарів вниз, компонент онука, який також вводив послугу і підписався на ту саму подію, може впоратися з нею. Будь-який обробник подій, який змінить спільну модель програми, автоматично поширюється на всі компоненти, що залежать від неї. Це, мабуть, найближчий еквівалент $scope.broadcast()з кутового 1. У наступному розділі описана ця ідея більш докладно.

Приклад спостережуваної служби, яка використовує службові події для розповсюдження змін

Ось приклад спостережуваної служби, яка використовує службові події для поширення змін. Коли TodoItem додано, сервіс посилає подію, сповіщаючи його абонентів компонентів.

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

Ось як кореневий компонент підписався на подію:

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

Дочірній компонент, вкладений у кілька рівнів, підписався на подію таким же чином:

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

Ось компонент, який викликає службу для запуску події (вона може перебувати в будь-якому місці дерева дерева компонентів):

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

Довідка: Визначення змін у кутових


27
Я бачив косою $ в декількох постах тепер для спостережуваного або EventEmitter - наприклад, itemAdded$. Це конвенція RxJS чи щось таке? Звідки це походить?
Марк Райкок

1
Гарна відповідь. Ви заявили: "Зміни в моделі додатків автоматично поширюються на всі компоненти, які прямо чи опосередковано пов'язуються з тією ж моделлю". У мене є думка, що це не зовсім так працює (але я не впевнений). В іншому дописі Савкіна наведено приклад компонента, що змінює streetвластивість моделі додатків, але оскільки Angular 2 реалізує виявлення змін за ідентичністю / довідкою, зміни не поширюються ( onChangesне називаються), оскільки посилання на модель додатка не змінилося ( продовження ...)
Марк Райкок

10
Можливо, ви хочете оновити свою відповідь, щоб у службі використовувати спостережуваний, а не EventEmitter. Див stackoverflow.com/a/35568924/215945 і stackoverflow.com/questions/36076700
Марк Rajcok

2
Так, суфікс $ - це конвенція RxJS, популяризована Cycle.js. cycle.js.org/…
jody tate

4
Ви не повинні вручну підписуватись на рівний темтер. Це може бути не помітним у фінальному випуску! Дивіться це: bennadel.com/blog/…
NetProvoke

49

Наведений нижче код є прикладом заміни $ range.emit () або $ range.broadcast () у Angular 2, використовуючи спільну службу для обробки подій.

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

Приклад використання:

Трансляція:

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

Слухач:

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

Він може підтримувати кілька аргументів:

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

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

Що це робить? статичні параметри отримання () {return [new Inject (EventsService)]; }
Beanwah

У цьому прикладі я використовую Ionic 2 Framework. Метод статичних параметрів викликається, коли викликається метод конструктора і використовується для подачі залежностей на конструктор. Розшифровка тут stackoverflow.com/questions/35919593 / ...
jim.taylor.1974

1
Чудово зроблено. Проста і забезпечує легко пристосовувану систему сповіщень для всього додатка, а не лише разову.
Майк М

Я щойно створив подібний сервіс із підтримкою символів. Сподіваюся, це допомагає. github.com/govorov/ng-radio
Станіслав Євгенович Говоров

2
Чудово, користувався ним, але додав функцію вимкнення, якщо більше вас цікавить: off(name, listener) { this.listeners[name] = this.listeners[name].filter(x => x != listener); }
LVDM

16

Я використовую послугу повідомлень, яка обгортає rxjs Subject(TypeScript)

Приклад Plunker: Служба повідомлень

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

Компоненти можуть передплачувати та транслювати події (відправник):

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

(приймач)

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

subscribeМетод MessageServiceповертає rxjs Subscriptionоб'єкт, який може бути відписався від приблизно так:

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

Дивіться також цю відповідь: https://stackoverflow.com/a/36782616/1861779

Приклад Plunker: Служба повідомлень


2
дуже цінний. Дякую за відповідь. Щойно я з'ясував, що ви не можете спілкуватися з двома компонентами у двох різних модулях таким чином. Для досягнення мети мені довелося зареєструвати MessageService на рівні app.module, додавши туди провайдерів. Будь-який спосіб це дійсно класний спосіб.
Rukshan Dangalla

це все жахливо застаріло. особливо планкер, який не завантажує жодних ресурсів успішно. вони всі 500 кодів помилок.
тацу

Я отримуюProperty 'filter' does not exist on type 'Subject<EventMessage>'.
Дрю

@Drew, у нових версіях RxJS використовується this.handler.pipe(filter(...)). Див орендованої оператори .
т.888

1
@ t.888 спасибі, я зрозумів це. Оновлена ​​функція підписки виглядає такreturn this.handler.pipe( filter(message => message.type === type), map(message => message.payload) ).subscribe(callback);
Дрю

12

НЕ використовуйте EventEmitter для спілкування служб.

Ви повинні використовувати один із типів спостереження. Мені особисто подобається BehaviorSubject.

Простий приклад:

Ви можете пройти початковий стан, тут я проходжу null

нехай предмет = новий BehaviorSubject (null);

Коли ви хочете оновити тему

subject.next (мій об’єкт)

Слідкуйте за будь-якою службою чи компонентом та дійте, коли вона отримує нові оновлення.

subject.subscribe (this.YOURMETHOD);

Ось додаткова інформація. .


1
Ви могли б детальніше пояснити причини цього дизайнерського рішення?
mtraut

@mtraut це посилання також має вичерпне пояснення.
Даніял Калбасі

для більш детального пояснення, як користуватися BehaviourSubject, будь ласка, прочитайте цю статтю blog.cloudboost.io/…
rafalkasa

Точно те, що мені було потрібно. Приємно і просто :)
Низький

8

Ви можете використовувати EventEmitter або спостережувані дані для створення служби подій, що зареєстрована в DI. Кожен компонент, який хоче брати участь, просто запитує послугу як параметр конструктора і випромінює та / або підписується на події.

Дивитися також


2

Мій улюблений спосіб - це використання в моїй службі випромінювача тематики поведінки або події (майже однаковий) для контролю над усім моїм підкомпонентом.

Використовуючи кутовий кліп, запустіть ng gs для створення нової послуги, а потім використовуйте BehaviorSubject або 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);
   }
}

Коли ви зробите це, кожен компонент, який використовує вашу послугу в якості постачальника, буде відомий про зміни. Просто підпишіться на результат, як ви робите з 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

Я створив зразок pub-sub тут:

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

Ідея полягає у використанні суб'єктів RxJs для передачі спостереження та та спостережних даних як загальне рішення для випромінювання та підписки на користувацькі події. У моїй вибірці я використовую об’єкт клієнта для демонстраційних цілей

this.pubSubService.Stream.emit(customer);

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

Ось також демонстрація в прямому ефірі: http://www.syntaxsuccess.com/angular-2-samples/#/demo/pub-sub


1

Це моя версія:

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

використання:

export class <YOURCOMPONENT> implements IEventListener{

  constructor(private eventManager: EventManagerService) {


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


  }

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

}

випромінювати:

 this.eventManager.emit("EVENT_NAME");

0

Ми реалізували ngModelChange директиву, яка надсилає всі зміни моделі через емітер подій, який ви інстанціюєте у своєму власному компоненті. Вам просто потрібно прив’язати свій емітер подій до директиви.

Дивіться: https://github.com/atomicbits/angular2-modelchangeobservable

У html прив'яжіть емітер події (країнаЗмінена в цьому прикладі):

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

У своєму компоненті typecript виконайте кілька операцій з асинхронізацією на 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

Сервісні події: Компоненти можуть підписатися на сервісні події. Наприклад, два компоненти братів можуть брати підписку на одну службову подію та відповідати, змінюючи відповідні моделі. Детальніше про це нижче.

Але обов'язково скасуйте підписку на знищення батьківського компонента.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.