Глобальные события в Angular

Разве в Angular нет эквивалента $scope.emit() или $scope.broadcast()?

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

Что делать, если мне нужно общаться между FX. братья или сестры или между компонентом в корне DOM и элементом, вложенным в несколько уровней?

Ответ 1

Не существует эквивалента $scope.emit() или $scope.broadcast() от AngularJS. EventEmitter внутри компонента подходит близко, но, как вы упомянули, он отправляет событие только непосредственному родительскому компоненту.

В Angular есть другие альтернативы, которые я постараюсь объяснить ниже.

Привязки @Input() позволяют связать модель приложения в ориентированном графе объектов (от корня до листьев). Поведение по умолчанию стратегии детектора изменений компонента заключается в распространении всех изменений в модели приложения для всех привязок из любого подключенного компонента.

Помимо: есть два типа моделей: модели просмотра и прикладные модели. Модель приложения связана через привязки @Input(). Модель представления - это просто свойство компонента (не декорированное @Input()), которое связано в шаблоне компонента.

Чтобы ответить на ваши вопросы:

Что делать, если мне нужно общаться между родственными компонентами?

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

  2. События компонента: дочерние компоненты могут отправлять событие родительскому компоненту с помощью привязок @Output(). Родительский компонент может обрабатывать событие и манипулировать моделью приложения или собственной моделью представления. Изменения в модели приложения автоматически распространяются на все компоненты, которые прямо или косвенно связаны с одной и той же моделью.

  3. Сервисные события: Компоненты могут подписаться на сервисные события. Например, два дочерних компонента могут подписаться на одно и то же сервисное событие и ответить, изменив свои соответствующие модели. Подробнее об этом ниже.

Как я могу общаться между корневым компонентом и компонентом, вложенным в несколько уровней?

  1. Модель общего приложения: модель приложения может быть передана из корневого компонента в глубоко вложенные подкомпоненты через привязки @Input(). Изменения в модели из любого компонента будут автоматически распространяться на все компоненты, которые совместно используют одну и ту же модель.
  2. События службы: вы также можете переместить EventEmitter в общую службу, которая позволяет любому компоненту внедрить службу и подписаться на событие. Таким образом, корневой компонент может вызывать метод службы (обычно изменяющий модель), который, в свою очередь, генерирует событие. Несколько уровней вниз, дочерний компонент, который также внедрил службу и подписался на то же событие, может обработать его. Любой обработчик событий, который изменяет общую модель приложения, будет автоматически распространяться на все компоненты, которые зависят от нее. Вероятно, это наиболее близкий эквивалент $scope.broadcast() из Angular 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));
    }
}

Ссылка: Обнаружение изменений в угловых

Ответ 2

Следующий код в качестве примера замены $ scope.emit() или $ scope.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);
});

Ответ 3

Я использую сервис сообщений, оборачивает в rxjs Subject (машинопись)

Пример 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 возвращает объект Subscription rxjs, от которого можно отказаться, например:

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

Также смотрите этот ответ: fooobar.com/questions/39872/...

Пример Plunker: служба сообщений

Ответ 4

НЕ используйте EventEmitter для служебного общения.

Вы должны использовать один из наблюдаемых типов. Мне лично нравится BehaviorSubject.

Простой пример:

Вы можете передать начальное состояние, здесь я пропустил ноль

let subject = new BehaviorSubject (null);

Когда вы хотите обновить тему

subject.next(MyObject)

Наблюдайте от любого сервиса или компонента и действуйте, когда он получает новые обновления.

subject.subscribe(this.YOURMETHOD);

Здесь больше информации. ,

Ответ 5

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

См. также

Ответ 6

Мой любимый способ - это использовать субъект поведения или события (почти то же самое) в моей службе для управления всем моим подкомпонентом.

Используя angular cli, запустите ng g s для создания новой службы, затем используйте 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
  }
}

Ответ 7

Я создал образец pub-sub здесь:

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

Идея состоит в том, чтобы использовать объекты RxJs для подключения Observer и Observables в качестве общего решения для выпуска и подписки на пользовательские события. В моем примере я использую объект клиента для демонстрационных целей

this.pubSubService.Stream.emit(customer);

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

Вот живая демонстрация: http://www.syntaxsuccess.com/angular-2-samples/#/demo/pub-sub

Ответ 8

Это моя версия:

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

Ответ 9

Мы внедрили ngModelChange наблюдаемую директиву, которая отправляет все изменения модели через эмитент событий, который вы создаете в своем собственном компоненте. Вам просто нужно привязать свой эмиттер событий к директиве.

Смотрите: https://github.com/atomicbits/angular2-modelchangeobservable

В html свяжите свой эмиттер событий (countryChanged в этом примере):

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

В вашем компоненте typescript выполните некоторые асинхронные операции с 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)
                })
            })
    }
}

Ответ 10

Служебные события. Компоненты могут подписаться на события службы. Например, два компонента sibling могут подписаться на одно и то же событие службы и ответить, изменив их соответствующие модели. Подробнее об этом ниже.

Но не забудьте отказаться от подписки на уничтожение родительского компонента.

Ответ 11

Еще один способ связи между компонентами и службами - использование поставщика событий, используемого в Ionic.

Я предпочитаю синтаксис "публиковать" и "подписываться", на мой взгляд, более читаемый.

Просто скопируйте этот сервис в ваш проект.

import {Injectable} from '@angular/core';

export type EventHandler = (...args: any[]) => any;

@Injectable({
    providedIn: 'root',
})
export class Events {
    private c = new Map<string, EventHandler[]>();

/**
 * Subscribe to an event topic. Events that get posted to that topic will trigger the 
provided handler.
 *
 * @param topic the topic to subscribe to
 * @param handler the event handler
 */
subscribe(topic: string, ...handlers: EventHandler[]) {
    let topics = this.c.get(topic);
    if (!topics) {
        this.c.set(topic, topics = []);
    }
    topics.push(...handlers);
}

/**
 * Unsubscribe from the given topic. Your handler will no longer receive events published to this topic.
 *
 * @param topic the topic to unsubscribe from
 * @param handler the event handler
 *
 * @return true if a handler was removed
 */
unsubscribe(topic: string, handler?: EventHandler): boolean {
    if (!handler) {
        return this.c.delete(topic);
    }

    const topics = this.c.get(topic);
    if (!topics) {
        return false;
    }

    // We need to find and remove a specific handler
    const index = topics.indexOf(handler);

    if (index < 0) {
        // Wasn't found, wasn't removed
        return false;
    }
    topics.splice(index, 1);
    if (topics.length === 0) {
        this.c.delete(topic);
    }
    return true;
}

/**
 * Publish an event to the given topic.
 *
 * @param topic the topic to publish to
 * @param eventData the data to send as the event
 */
publish(topic: string, ...args: any[]): any[] | null {
    const topics = this.c.get(topic);
    if (!topics) {
        return null;
    }
    return topics.map(handler => {
        try {
            return handler(...args);
        } catch (e) {
            console.error(e);
            return null;
        }
    });
}
}

Дополнительная информация о Ionic Events: Документация Ionic Events