238

Is there no equivalent to $scope.emit() or $scope.broadcast() in Angular?

I know the EventEmitter functionality, but as far as I understand that will just emit an event to the parent HTML element.

What if I need to communicate between fx. siblings or between a component in the root of the DOM and an element nested several levels deep?

amphetamachine
  • 27,620
  • 12
  • 60
  • 72
skovmand
  • 4,372
  • 5
  • 25
  • 30
  • 2
    I had a similar question related to creating a dialog component that could be accessed from any point in dom: http://stackoverflow.com/questions/34572539/how-to-make-a-component-universally-accessible-in-angular2 Basically, one solution is to put an event emitter in a service – brando Jan 18 '16 at 17:35
  • 1
    Here is my implementation of such a service using RXJS that allows to get the nth last values upon subscription. https://stackoverflow.com/questions/46027693/angular2-4-broadcast-service-using-rxjs – Codewarrior Sep 03 '17 at 20:57

10 Answers10

409

There is no equivalent to $scope.emit() or $scope.broadcast() from AngularJS. EventEmitter inside of a component comes close, but as you mentioned, it will only emit an event to the immediate parent component.

In Angular, there are other alternatives which I'll try to explain below.

@Input() bindings allows the application model to be connected in a directed object graph (root to leaves). The default behavior of a component's change detector strategy is to propagate all changes to an application model for all bindings from any connected component.

Aside: There are two types of models: View Models and Application Models. An application model is connected through @Input() bindings. A view model is a just a component property (not decorated with @Input()) which is bound in the component's template.

To answer your questions:

What if I need to communicate between sibling components?

  1. Shared Application Model: Siblings can communicate through a shared application model (just like angular 1). For example, when one sibling makes a change to a model, the other sibling that has bindings to the same model is automatically updated.

  2. Component Events: Child components can emit an event to the parent component using @Output() bindings. The parent component can handle the event, and manipulate the application model or its own view model. Changes to the Application Model are automatically propagated to all components that directly or indirectly bind to the same model.

  3. Service Events: Components can subscribe to service events. For example, two sibling components can subscribe to the same service event and respond by modifying their respective models. More on this below.

How can I communicate between a Root component and a component nested several levels deep?

  1. Shared Application Model: The application model can be passed from the Root component down to deeply nested sub-components through @Input() bindings. Changes to a model from any component will automatically propagate to all components that share the same model.
  2. Service Events: You can also move the EventEmitter to a shared service, which allows any component to inject the service and subscribe to the event. That way, a Root component can call a service method (typically mutating the model), which in turn emits an event. Several layers down, a grand-child component which has also injected the service and subscribed to the same event, can handle it. Any event handler that changes a shared Application Model, will automatically propagate to all components that depend on it. This is probably the closest equivalent to $scope.broadcast() from Angular 1. The next section describes this idea in more detail.

Example of an Observable Service that uses Service Events to Propagate Changes

Here is an example of an observable service that uses service events to propagate changes. When a TodoItem is added, the service emits an event notifying its component subscribers.

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

Here is how a root component would subscribe to the event:

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

A child component nested several levels deep would subscribe to the event in the same way:

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

Here is the component that calls the service to trigger the event (it can reside anywhere in the component tree):

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

Reference: Change Detection in Angular

danronmoon
  • 3,814
  • 5
  • 34
  • 56
Michael Kang
  • 52,003
  • 16
  • 103
  • 135
  • 30
    I've seen the trailing $ in a few posts now for an observable or EventEmitter -- e.g., `itemAdded$`. Is that an RxJS convention or something? Where does this come from? – Mark Rajcok Jan 12 '16 at 03:29
  • 1
    Nice answer. You stated, "Changes to the App Model are automatically propagated to all components that directly or indirectly bind to the same model." I have a hunch that it doesn't quite work this way (but I'm not sure). Savkin's other [blog post](http://victorsavkin.com/post/133936129316/angular-immutability-and-encapsulation) gives an example of a component changing the `street` property of the app model, but since Angular 2 implements change detection by identity/reference, no changes are propagated (`onChanges` is not called), because the app model reference hasn't changed (cont...) – Mark Rajcok Jan 12 '16 at 03:44
  • I'll try to find where I got the $ convention from. To be honest I can't recall where, but I found the convention useful (hopefully is a convention that recommended by the community). – Michael Kang Jan 12 '16 at 03:46
  • (cont) so none of the @input properties will have changed. But, it works because Angular "has to be conservative and check [all of the components'] templates on every browser event." I believe the components all have a reference to the same/one app model. So when a property on that model changes, they all see the change because there is only one object. All of the templates are reevaluated, so it looks like something got propagated, but nothing did. Well, that's my understanding at this point. – Mark Rajcok Jan 12 '16 at 03:48
  • I like the $ convention also. – Mark Rajcok Jan 12 '16 at 03:48
  • I think in the blog post you referenced, he's talking about optimizations (i.e. changing the change detection strategy to OnPush for immutable bindings, or bindings on observables). Based on this post: http://victorsavkin.com/post/110170125256/change-detection-in-angular-2, he mentions "Angular has to be conservative and run all the checks every single time...". I've found this to be true. All bindings are indeed checked by default regardless of whether or not the reference has changed. I believe the section on immutability and observables is about optimizing the default strategy. – Michael Kang Jan 12 '16 at 03:53
  • I guess my point (after all of those comments) is that when we say that the application model changed, we have to say what exactly is changing. Is the reference to the application model changing (i.e., you created a brand new (maybe immutable) app model), or is one or more property values of the application model changing? If the reference is changed, then I believe it propagates down. If a property changes, then I don't think there is any propagation. Again, because Savkin says that `ngOnChanges()` is not called if only some property is changed. – Mark Rajcok Jan 12 '16 at 03:54
  • My understanding is that checking the template bindings is different from propagating @input bindings. (This stuff is so confusing.) – Mark Rajcok Jan 12 '16 at 03:57
  • Agreed, I wish there was better documentation:) Unfortunately, I don't have any definitive reference to be 100% sure, but based what I've learned here or there, I've come to the understanding that change detection starts top down when all the bindings are initialized (or propagated). After that, its a browser event (or event emitter) that will trigger change detection. This marks all components from root to the leaf, so that change detection runs along this path. The default strategy is to run change detection from root to leaves, for all bindings (including the targeted path). – Michael Kang Jan 12 '16 at 04:04
  • I agree with everything you said in the last two comments. Here's a [plunker](http://plnkr.co/edit/QoKGNFDTzmLIQbfY9Gao?p=preview) showing how changing an app model property does not result in `ngOnChanges()` being called, but changing the app model reference, by creating a new object, does result in `ngOnChanges()` being called. (Have the console open to see the log messages.) I think it is important to highlight that **if an app model property value changes `ngOnChanges()` is not called**. To me, that means no propagation... but template bindings still get re-evaluated. I'll stop now :) – Mark Rajcok Jan 12 '16 at 04:27
  • 1
    I agree with your comments as well. I'm sure these extended comments are helpful to others:) – Michael Kang Jan 12 '16 at 04:29
  • 13
    You might want to update your answer to use an Observable instead of an EventEmitter in the service. See http://stackoverflow.com/a/35568924/215945 and http://stackoverflow.com/questions/36076700 – Mark Rajcok Mar 23 '16 at 14:39
  • 4
    Yes, the suffixed $ is an RxJS convention popularized by Cycle.js. http://cycle.js.org/basic-examples.html#what-does-the-suffixed-dollar-sign-mean – jody tate Jul 09 '16 at 19:09
  • 4
    You should no manually subscribe to an eventemitter. It might not be an observable in the final release! See this: http://www.bennadel.com/blog/3038-eventemitter-is-an-rxjs-observable-stream-in-angular-2-beta-6.htm#comments_47949 – dotnet-provoke Aug 22 '16 at 03:55
  • What is the difference between subscribing to the service in the constructor or in ngOnInit()? – David Prieto Aug 24 '18 at 06:41
  • 2
    To @MarkRajcok:I found the next [possible description](https://angular.io/guide/rx-library#naming-conventions-for-observables): `Although the Angular framework does not enforce a naming convention for observables, you will often see observables named with a trailing “$” sign.` – MegaCasper Jan 11 '19 at 17:53
  • Link to change detection should be: https://vsavkin.com/change-detection-in-angular-2-4f216b855d4c – sfs Feb 18 '22 at 08:21
52

The following code as an example of a replacement for $scope.emit() or $scope.broadcast() in Angular 2 using a shared service to handle events.

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

Example usage:

Broadcast:

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

Listener:

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

It can support multiple arguments:

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

this.eventsService.on('something', function (a, b, c) {
   console.log(a, b, c);
});
jim.taylor.1974
  • 3,493
  • 1
  • 17
  • 11
  • What does this do? static get parameters() { return [new Inject(EventsService)]; } – Beanwah Jul 09 '16 at 20:31
  • In this example I am using Ionic 2 Framework. The static parameters method is called when the constructor method is invoked and is used to supply the dependencies to the constructor. Explanation here http://stackoverflow.com/questions/35919593/what-is-static-get-parameters-purpose-in-ionic-2 – jim.taylor.1974 Jul 13 '16 at 00:07
  • 2
    Nicely done. Simple and provides an easily adaptable notification system for the entire app, not just a one-off. – Mike M Oct 25 '16 at 17:10
  • I just created similar service with wildcards support. Hope it helps. https://github.com/govorov/ng-radio – Stanislav E. Govorov Nov 22 '16 at 22:12
  • I might be wrong, but with some changes the `EventsService` class can work as a global event handler without using `RxJS` at all, so, what is the benefit here? – Castro Roy Dec 01 '16 at 18:00
  • Thanks! I've used this code in my projects, but want to mention one thing for others who want to use this code: in EventsService there should be some code for allowing to unsubscribe from global events, because during development I've faced an issue, when component was destroyed, and then after it was initialized again, I've had two subscriptions instead of one (like when we have some login component, enter app, then logout, then login, and all subscriptions are duplicated). So be careful. – Артур Пипченко Oct 13 '17 at 14:15
  • 2
    Awesome, used it but added an off function if anymore is interested: `off(name, listener) { this.listeners[name] = this.listeners[name].filter(x => x != listener); }` – LVDM Dec 05 '17 at 12:37
  • using this implementation without "off" method caused serious memory leak. Don't forget to remove listeners otherwise components will hang around. – robert Jan 29 '19 at 22:20
  • if HttpErrorHandler is not injected into any component that is loaded, then the code is not executed – Reza Oct 07 '19 at 20:01
18

I'm using a message service that wraps an rxjs Subject (TypeScript)

Plunker example: Message Service

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

Components can subscribe and broadcast events (sender):

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

(receiver)

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

The subscribe method of MessageService returns an rxjs Subscription object, which can be unsubscribed from like so:

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

Also see this answer: https://stackoverflow.com/a/36782616/1861779

Plunker example: Message Service

David R
  • 14,711
  • 7
  • 54
  • 72
t.888
  • 3,862
  • 3
  • 25
  • 31
  • 2
    very valuable. Thanks for the answer. I just found out you can't communicate with two components in two *different modules* using this way. To achieve the goal I had to register MessageService in the app.module level by adding providers there. Any way this is a really cool way. – Rukshan Dangalla Sep 17 '17 at 07:46
  • this is all woefully out of date. especially the plunker which doesn't load any resources successfully. they're all 500 error codes. – tatsu Aug 29 '19 at 08:25
  • I get `Property 'filter' does not exist on type 'Subject'.` – Drew Nov 18 '19 at 16:58
  • @Drew, on newer versions of RxJS use `this.handler.pipe(filter(...))`. See [lettable operators](https://blog.angularindepth.com/rxjs-understanding-lettable-operators-fe74dda186d3). – t.888 Nov 18 '19 at 20:28
  • 1
    @t.888 thanks, I figured it out. The updated subscribe function looks like `return this.handler.pipe( filter(message => message.type === type), map(message => message.payload) ).subscribe(callback);` – Drew Nov 19 '19 at 14:52
13

DO Not Use EventEmitter for your service communication.

You should use one of the Observable types. I personally like BehaviorSubject.

Simple example:

You can pass initial state, here I passing null

let subject = new BehaviorSubject(null);

When you want to update the subject

subject.next(myObject)

Observe from any service or component and act when it gets new updates.

subject.subscribe(this.YOURMETHOD);

Here is more information..

Danial Kalbasi
  • 434
  • 5
  • 9
8

You can use EventEmitter or observables to create an eventbus service that you register with DI. Every component that wants to participate just requests the service as constructor parameter and emits and/or subscribes to events.

See also

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
2

My favorite way to do is by using behavior subject or event emitter (almost the same) in my service to control all my subcomponent.

Using angular cli, run ng g s to create a new service then use a BehaviorSubject or 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);
   }
}

When you do that every component using your service as a provider will be aware of the change. Simply subscribe to the result like you do with 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

I have created a pub-sub sample here:

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

The idea is to use RxJs Subjects to wire up an Observer and and Observables as a generic solution for emitting and subscribing to custom events. In my sample I use a customer object for demo purposes

this.pubSubService.Stream.emit(customer);

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

Here is a live demo as well: http://www.syntaxsuccess.com/angular-2-samples/#/demo/pub-sub

TGH
  • 38,769
  • 12
  • 102
  • 135
1

This is my version:

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

use:

export class <YOURCOMPONENT> implements IEventListener{

  constructor(private eventManager: EventManagerService) {


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


  }

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

}

emit:

 this.eventManager.emit("EVENT_NAME");
zakrzu
  • 485
  • 4
  • 9
0

We implemented a ngModelChange observable directive that sends all model changes through an event emitter that you instantiate in your own component. You simply have to bind your event emitter to the directive.

See: https://github.com/atomicbits/angular2-modelchangeobservable

In html, bind your event emitter (countryChanged in this example):

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

In your typescript component, do some async operations on the 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)
                })
            })
    }
}
Peter Rigole
  • 201
  • 1
  • 4
-1

Service Events: Components can subscribe to service events. For example, two sibling components can subscribe to the same service event and respond by modifying their respective models. More on this below.

But make sure to unsubscribe to that on destroy of the parent component.

B.V.S Bharat Kumar
  • 202
  • 1
  • 5
  • 14