Angular 2: Component communication with events vs callbacks

So a child component needs to pass some data to its parent. I use TypeScript and in this blog I’ll show you two techniques: events vs callbacks.

Emitting events

The technique with emitting events is well documented:

1. Declare a property of type EventEmitter and instantiate it
2. Mark it with an @Output annotation
3. Be nice. I mean use generics to make it obvious to other developer what are you emitting and let TypeScript compiler warn you it you’ll try me emit the object of the wrong type.

The next line shows you an example that implements all three steps in a component called PriceQuoterComponent, which will be emitting price quotes of type IPriceQuote.

@Output() lastPrice: EventEmitter <IPriceQuote> = 
                              new EventEmitter();

The above code is very declarative and easy to read even after I quit and will go to work for another company.

To actually emit/dispatch/fire the lastPrice event, you need to create an object of type IPriceQuote and invoke the function emit:

let priceQuote: IPriceQuote = {...};
this.lastPrice.emit(priceQuote);

Nice an clean. Our component is loosely coupled and reusable, because it has no strings attached to any other components. It just shoots the lastPrice event with a payload to whoever cares to listen (or subscribe) to this event. For example, a parent component can do it like this:

<price-quoter (lastPrice)="priceQuoteHandler($event)">
</price-quoter>

The event handler function is also very easy to understand, especially because its argument explicitly declares the type of the object its getting:

priceQuoteHandler(event:IPriceQuote) {...}

Callbacks

Not sure why do you even want to go this route. Most likely, because you’ve never been in the callback hell. Get ready to lose the goodness of explicit typing and be prepared to deal with the “this and that” problem. But if you insist, I’ll show you how to do it.

This time the child (PriceQuoterComponent) will declare an @Input property, which will accept the name of the callback function to be called on the parent. Remember the Hollywood principle “Don’t call me, I’ll call you”?

@Input() whoToCall: Function;

The parent will bind the name of its function to call. Now, the child instead of emitting the event will call that function on its parent:

let priceQuote: IPriceQuote = {...};

this.whoToCall(priceQuote);

The parent won’t be listening to child’s events because there won’t be any. But now the parent needs to do two things:

1. Declare and bind the function that has to be called by the child

2. Ensure that it’ll be invoked in the object that represents the parent and not on a global object. We need to pass the “this” object to the bound function, which can be done using arrow functions in ES6 or the function bind() in ES5:

@Component({
...
    template: `
   <price-quoter [whoToCall] ="fnPriceQuoteHandler">
   </price-quoter>`
})
class AppComponent {

    fnPriceQuoteHandler: Function;

    ngOnInit(){
        this.fnPriceQuoteHandler= 
              obj => this.priceQuoteHandler(obj); // ES6

        // this.fnPriceQuoteHandler=
        //     this.priceQuoteHandler.bind(this); // ES5
    }
}    

In this video I did a quick code overview of both implementations of the above components. The sources are here. I like the version with events better, and you?

3 thoughts on “Angular 2: Component communication with events vs callbacks

  1. I agree that this is an important choice to consider, had to decide on this recently, and I like the event pattern as well; but I don’t think the decision is that straightforward. (I am somewhat new to front-end development, so take this with a grain of salt.)

    For starters, nothing prevents one from having strongly typed callback function; TypeScript is perfectly capable of describing that. The callback function should be typed as function taking IPriceQuote and returning void, e.g. “(quote:IPriceQuote) => void”. So strong typing vs. lack thereof is not a very strong argument in deciding whether to use events or callbacks. (Incidentally, the type you used for callback, “Function”, maps to an interface that I am pretty sure is not intended to be used in this context.)

    More importantly, however, is the fact that these Angular 2 events can only be used to communicate between immediate child and immediate parent, and cannot be used to communicate across a deeper component hierarchy. This is something that was not immediately clear to me when I started using Angular 2, and looking into it I discovered that whether this is a limitation or a feature is (still?) a subject of debate. Now, whether it is a good idea to communicate beyond immediate child/parent boundary is not clear cut-either, though I think this need could be more common these days, with deeper component hierarchies, and especially with separation between container vs. presentation components, which introduces extra levels. In these cases, passing down a callback appears to be an easier and maybe cleaner solution: with events one has to explicitly handle and forward the event at each level of the hierarchy.

    Consequently, my current approach to this is something like: use events for leaf/presentational components, or in cases where the immediate parent is clearly the only consumer of this output; use callbacks for communicating along deeper component hierarchies. Time will tell how well this works.

    (One more minor thing: Important as binding is, the reason the callback did not work in the video with everything commented out, is not the lack of binding but the fact that fnPriceQuoteHandler was not assigned at all …)

    Thank you for your writing!

  2. Agree, using events vs. functions is a matter of personal preference. On the other hand, you can still use event bubbling if you don’t want to handle the event in the immediate parent. At this point Angular 2 doesn’t offer any special syntax for event bubbling, but you can use native events for this. Here’s a sample app that illustrate this: https://github.com/Farata/angular2typescript/blob/master/chapter6/inter_comp_communications/app/binding/native-event-bubbling.ts

    As to why fnPriceQuoteHandle doesn’t work when everything commented out, you’re right 🙂 While recording the video I said that and immediately realized that this was wrong, but was too lazy to edit the video. Now that you’ve noticed this, I need to find time and cut out that fragment. Thanks.

Leave a reply to Alex Cancel reply