Angularのグローバルイベント


224

相当するものが存在しない$scope.emit()か、$scope.broadcast()角度では?

私はEventEmitter機能を知っていますが、理解している限り、それは親HTML要素にイベントを発行するだけです。

fx間で通信する必要がある場合はどうなりますか?兄弟、またはDOMのルートにあるコンポーネントと、いくつかのレベルでネストされた要素との間の深さ?


2
domの任意のポイントからアクセスできるダイアログコンポーネントの作成に関連して同様の質問がありました: stackoverflow.com/questions/34572539/… 基本的に、1つの解決策は、イベントエミッターをサービスに配置することです
brando

1
サブスクリプション時に最後からn番目の値を取得できるRXJSを使用したこのようなサービスの実装を以下に示します。stackoverflow.com/questions/46027693/...
CodeWarriorの

回答:


385

相当するものはありません$scope.emit()$scope.broadcast()AngularJSからは。コンポーネント内のEventEmitterが近くなりますが、前述のとおり、直接の親コンポーネントにのみイベントを発行します。

Angularには、以下で説明しようとする他の選択肢があります。

@Input()バインディングを使用すると、アプリケーションモデルを有向オブジェクトグラフ(ルートからリーフ)に接続できます。コンポーネントの変更検出戦略のデフォルトの動作は、接続されているコンポーネントからのすべてのバインディングのすべての変更をアプリケーションモデルに伝播することです。

補足:モデルには、ビューモデルとアプリケーションモデルの2種類があります。アプリケーションモデルは、@ Input()バインディングを介して接続されます。ビューモデルは、コンポーネントのテンプレートにバインドされている単なるコンポーネントプロパティ(@Input()で装飾されていない)です。

あなたの質問に答えるには:

兄弟コンポーネント間で通信する必要がある場合はどうなりますか?

  1. 共有アプリケーションモデル:兄弟は、共有アプリケーションモデル(angular 1と​​同様)を介して通信できます。たとえば、1つの兄弟がモデルに変更を加えると、同じモデルへのバインディングを持つ他の兄弟が自動的に更新されます。

  2. コンポーネントイベント:子コンポーネントは、@ Output()バインディングを使用して親コンポーネントにイベントを発行できます。親コンポーネントはイベントを処理し、アプリケーションモデルまたはそれ自体のビューモデルを操作できます。アプリケーションモデルへの変更は、同じモデルに直接または間接的にバインドするすべてのコンポーネントに自動的に伝達されます。

  3. サービスイベント:コンポーネントはサービスイベントをサブスクライブできます。たとえば、2つの兄弟コンポーネントが同じサービスイベントをサブスクライブし、それぞれのモデルを変更することで応答できます。これについては、以下で詳しく説明します。

ルートコンポーネントと、いくつかのレベルにネストされたコンポーネントとの間の通信方法を教えてください。

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

参照:Angularでの変更検出


27
いくつかの投稿で、監視可能なまたはEventEmitterの末尾の$を確認しましたitemAdded$。それはRxJSの慣習ですか?これはどこから来たのですか?
Mark Rajcok、2016年

1
素敵な答え。「アプリモデルへの変更は、同じモデルに直接または間接的にバインドするすべてのコンポーネントに自動的に反映されます。」私はそれがこのようにうまく機能しないという予感を持っています(しかし、私にはわかりません)。Savkinの他のブログ投稿でstreet、アプリモデルのプロパティを変更するコンポーネントの例を示していますが、Angular 2はID /参照による変更検出を実装しているonChangesため、アプリモデル参照が変更されていないため(変更は伝達されません)(呼び出されません)続き...)
Mark Rajcok

10
サービスでEventEmitterの代わりにObservableを使用するように回答を更新したい場合があります。stackoverflow.com/a/35568924/215945およびstackoverflow.com/questions/36076700を
Mark Rajcok

2
はい、サフィックスが付いた$は、Cycle.jsによって一般化されたRxJS規約です。cycle.js.org/...
ジョディ・テイト

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
よくできました。シンプルで、1回限りではなく、アプリ全体に簡単に適用できる通知システムを提供します。
Mike M

ワイルドカードをサポートする同様のサービスを作成しました。それが役に立てば幸い。github.com/govorov/ng-radio
Stanislav E. Govorov

2
すばらしい、それを使用したが、興味がある場合はオフ機能を追加: 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メソッドはMessageServicerxjs 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

プランカーの例:メッセージサービス


2
非常に価値のある。答えてくれてありがとう。この方法では、2つの異なるモジュールの 2つのコンポーネントと通信できないことがわかりました。目標を達成するには、そこにプロバイダーを追加して、app.moduleレベルでMessageServiceを登録する必要がありました。とにかく、これは本当にクールな方法です。
Rukshan Dangalla 2017

これはすごく時代遅れです。特に、リソースを正常にロードしないプランカー。それらはすべて500エラーコードです。
tatsu

私が得るProperty 'filter' does not exist on type 'Subject<EventMessage>'.
ドリューは

@ Drew、RxJSの新しいバージョンではを使用しますthis.handler.pipe(filter(...))lettable演算子を参照してください。
t.888 2019年

1
@ t.888ありがとう、私はそれを理解しました。更新された購読機能は次のようになりますreturn this.handler.pipe( filter(message => message.type === type), map(message => message.payload) ).subscribe(callback);
Drew

12

サービス通信にEventEmitterを使用しないでください

監視可能なタイプのいずれかを使用する必要があります。個人的にはBehaviorSubjectが好きです。

簡単な例:

初期状態を渡すことができますが、ここではnullを渡します

件名=新しいBehaviorSubject(null);

件名を更新したいとき

subject.next(myObject)

任意のサービスまたはコンポーネントから観察し、新しい更新を取得したときに行動します。

subject.subscribe(this.YOURMETHOD);

詳細はこちらです。


1
この設計上の決定の理由について詳しく教えてください。
mtraut 2017

@mtrautそのリンクにも包括的な説明があります。
Danial Kalbasi 2017

BehaviourSubjectの使用方法の詳細については、この記事を読んでください。blog.cloudboost.io/…
rafalkasa

まさに私が必要としたもの。ナイスでシンプル:)

8

あなたは使用することができます持つEventEmitterまたはあなたがDIに登録することeventbusサービスを作成するために観測を。参加を希望するすべてのコンポーネントは、コンストラクタパラメータとしてサービスを要求し、イベントを発行またはサブスクライブします。

こちらもご覧ください


2

私のお気に入りの方法は、サービスで動作サブジェクトまたはイベントエミッター(ほぼ同じ)を使用して、すべてのサブコンポーネントを制御することです。

Angular CLIを使用して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サブジェクトを使用して、カスタムイベントを発行およびサブスクライブするための一般的なソリューションとして、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


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で、イベントエミッターをバインドします(この例では、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)
                })
            })
    }
}

0

サービスイベント:コンポーネントはサービスイベントをサブスクライブできます。たとえば、2つの兄弟コンポーネントが同じサービスイベントをサブスクライブし、それぞれのモデルを変更することで応答できます。これについては、以下で詳しく説明します。

ただし、親コンポーネントの破棄時に必ずサブスクライブを解除してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.