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/215945stackoverflow.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.