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?