Angular의 글로벌 이벤트


224

Angular 와 동일 $scope.emit()하거나 $scope.broadcast()각도가 없습니까?

나는 EventEmitter기능을 알고 있지만, 그것이 이해하는 한 부모 HTML 요소에 이벤트를 방출합니다.

fx간에 통신해야하는 경우 어떻게합니까? 형제 또는 DOM 루트의 구성 요소와 여러 수준으로 중첩 된 요소 사이?


2
나는 대화 DOM에 어느 지점에서 액세스 할 수있는 구성 요소 생성에 관한 비슷한 질문했다 : stackoverflow.com/questions/34572539/... 기본적를 하나 개의 솔루션은 서비스에서 이벤트 이미 터를 배치하는 것입니다
— 브란도

1
다음은 구독시 n 번째 마지막 값을 얻을 수있는 RXJS를 사용하여 이러한 서비스를 구현 한 것입니다. stackoverflow.com/questions/46027693/…
— Codewarrior

답변:


385

에 상응하는 없습니다 $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));
    }
}

참조 : 각도 변화 감지


27
observable 또는 EventEmitter에 대한 몇 개의 게시물에서 $가 뒤따 랐습니다 (예 :) itemAdded$. 이것이 RxJS 규칙입니까? 어디에서 왔습니까?
— Mark Rajcok

1
좋은 대답입니다. "앱 모델에 대한 변경 사항은 동일한 모델에 직접 또는 간접적으로 바인딩되는 모든 구성 요소에 자동으로 전파됩니다." 나는 이런 식으로 작동하지 않는다는 직감을 가지고 있습니다 (그러나 확실하지 않습니다). Savkin의 다른 블로그 게시물 은 street앱 모델 의 속성을 변경하는 구성 요소의 예를 제공 하지만 Angular 2는 ID / 참조에 의한 변경 감지를 구현 onChanges하므로 앱 모델 참조가 변경되지 않았기 때문에 변경 사항이 전파 되지 않습니다 (호출되지 않음) 계속 ...)
— Mark Rajcok

10
서비스에서 EventEmitter 대신 Observable을 사용하도록 응답을 업데이트 할 수 있습니다. 참조 stackoverflow.com/a/35568924/215945 및 stackoverflow.com/questions/36076700
— 마크 Rajcok

2
예, 접미사 $는 Cycle.js에서 대중화 한 RxJS 규칙입니다. cycle.js.org/…
— jody tate

4
이벤트 이미 터를 수동으로 구독하면 안됩니다. 최종 릴리스에서는 관찰되지 않을 수 있습니다! 참조 : bennadel.com/blog/…
— NetProvoke

49

다음 코드 는 공유 서비스 를 사용하여 이벤트를 처리 하는 Angular 2의 $ scope.emit () 또는 $ scope.broadcast () 를 대체하는 예제입니다 .

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

이것은 무엇을 하는가? static get parameters () {return [new Inject (EventsService)]; }
— Beanwah

이 예에서는 Ionic 2 Framework를 사용하고 있습니다. 정적 매개 변수 메소드는 생성자 메소드가 호출 될 때 호출되며 생성자에 대한 종속성을 제공하는 데 사용됩니다. 여기 설명 stackoverflow.com/questions/35919593/…
— jim.taylor.1974

1
잘 했어요 간단하고 일회성뿐만 아니라 전체 앱에 대해 쉽게 적응할 수있는 알림 시스템을 제공합니다.
— Mike M

와일드 카드를 지원하는 비슷한 서비스를 만들었습니다. 도움이 되길 바랍니다. github.com/govorov/ng-radio
— Stanislav E. Govorov

2
굉장하고, 그것을 사용했지만 더 이상 관심이 있다면 off 기능을 추가했습니다. off(name, listener) { this.listeners[name] = this.listeners[name].filter(x => x != listener); }
— LVDM

16

rxjs Subject(TypeScript) 를 래핑하는 메시지 서비스를 사용하고 있습니다.

플 런커 예 : 메시지 서비스

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 : //.com/a/36782616/1861779

플 런커 예 : 메시지 서비스


2
매우 귀중한. 답변 해주셔서 감사합니다. 이 방법으로 두 개의 서로 다른 모듈 에서 두 개의 구성 요소와 통신 할 수 없다는 것을 알았습니다 . 목표를 달성하기 위해 거기에 공급자를 추가하여 app.module 수준에서 MessageService를 등록해야했습니다. 어쨌든 이것은 정말 멋진 방법입니다.
— Rukshan Dangalla

이 모든 것이 오래되었다. 특히 리소스를 성공적으로로드하지 않는 플런저. 그것들은 모두 500 오류 코드입니다.
— tatsu

나는 얻는다Property 'filter' does not exist on type 'Subject<EventMessage>'.
— Drew

최신 버전의 RxJS에서는 @Drew를 사용 this.handler.pipe(filter(...))합니다. lettable 연산자를 참조하십시오 .
— t.888

1
@ t.888 감사합니다, 나는 알아 냈습니다. 업데이트 된 구독 기능은 다음과 같습니다return this.handler.pipe( filter(message => message.type === type), map(message => message.payload) ).subscribe(callback);
— Drew

12

서비스 통신에 EventEmitter를 사용하지 마십시오 .

Observable 유형 중 하나를 사용해야합니다. 나는 개인적으로 BehaviorSubject를 좋아합니다.

간단한 예 :

초기 상태를 전달할 수 있습니다. 여기에서는 null을 전달합니다.

let subject = new BehaviorSubject (null);

주제를 업데이트하려는 경우

subject.next (myObject)

서비스 나 구성 요소를 관찰하고 새로운 업데이트를 받으면 작동합니다.

subject.subscribe (this.YOURMETHOD);

자세한 내용은 다음과 같습니다. .


1
이 디자인 결정의 이유를 자세히 설명해 주시겠습니까?
— mtraut

@mtraut 해당 링크에는 포괄적 인 설명이 있습니다.
— Danial Kalbasi

더 자세한 설명은 BehaviourSubject을 사용하는 방법이 문서 읽어 주시기 바랍니다 blog.cloudboost.io/...을
— rafalkasa

도덕적으로 내가 필요한 것. 친절하고 간단 :)
— 낮은


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

아이디어는 RxJ 주제를 사용하여 Observer 및 Observable을 사용자 정의 이벤트를 생성하고 구독하기위한 일반 솔루션으로 연결하는 것입니다. 샘플에서 데모 목적으로 고객 객체를 사용합니다.

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 observable 지시문을 구현했습니다. 이벤트 이미 터를 지시문에 바인딩하기 만하면됩니다.

참조 : https://github.com/atomicbits/angular2-modelchangeobservable

html에서 이벤트 이미 터를 바인드하십시오 (이 예에서는 countyChanged).

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

타이프 스크립트 컴포넌트에서 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

서비스 이벤트 : 구성 요소는 서비스 이벤트를 구독 할 수 있습니다. 예를 들어, 두 형제 구성 요소는 동일한 서비스 이벤트에 등록하고 해당 모델을 수정하여 응답 할 수 있습니다. 이에 대한 자세한 내용은 아래를 참조하십시오.

그러나 부모 구성 요소가 파괴되면 구독을 취소하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.