Forward Advance Learning

Angular 2 Foundations - Component Hierarchies and Communication - Event Emitters (Outputs)

Just as an anchor can emit a click event, and a form can emit a submit event, our custom components can emit events that we make up too.

For example, say we have a likes button. We might have it output a 'like' event and an 'unlike' event. If we have a rating star component, we might have it emit a 'rate' event. We might extend that event object to include the number of stars.

We need to do 3 things to make our component emit an event:

  1. Configure our component to emit the event.
  2. Create the event emitter in our component.
  3. Tell the event emitter to emit the event.

Let's do this now. We are going to create a like button. We click it and it emits a 'like' event.

1. Configure the component

First we need to add the 'liked' to our component decorator:

.Component({
selector: "like",
outputs: ["liked"],
template:
`
<a (click)="handleClick()">
Like?
</a>
`
})

This tells Angular that this component will emit a liked event. Notice also that the template is calling a function called handleClick() when we click it.

2. Create the EventEmitter

Next we need to create an attribute of our component object to manage the event. We do this in our class decorator:

.Class({
constructor: function() {
this.liked = new ng.core.EventEmitter();
}
})

This will be an instance of ng.core.EventEmitter.

Emit the event

Finally we need to tell our class to emit the event. Let's extend our Class to handle a click event:

.Class({
constructor: function() {
this.liked = new ng.core.EventEmitter();
this.handleClick = () => {
console.log('clicked');
this.liked.emit();
}
}
})

Putting it together

Here's the whole component:

var LikeButtonComponent = ng.core
.Component({
selector: "like",
outputs: ["liked"],
template:
`
<a (click)="handleClick()">
Like?
</a>
`
})
.Class({
constructor: function() {
this.liked = new ng.core.EventEmitter();
this.handleClick = function() {
console.log('clicked');
this.liked.emit();
}
}
});

Make use of the component

We can now make use of this component in a parent component, like so:

var AppComponent = ng.core
.Component({
selector: "app",
template:
`
<like (liked)="handleLike()">Hello</like>
{{message}}
`
})
.Class({
constructor: function() {
this.handleLike = function() {
this.message = "Thanks for liking us"
}
}
});

Notice the like component is now able to emit a liked event, which we can handle.

Exercise - like counter

Extend the code above so that we can count the number of likes. When the like counter emits a like event, have the counter in the parent template increase by one.

A user can like as many times as they want to.

Harder Exercise - Upvoting users

Change the above into an upvote/downvote component. Upvoting emits an upvote event. Downvoting emits a downvote event.

Now add this to your User component. Bind to the upvote in the user, such that when we click upvote, the user's voteCount property is increased. Vice-versa for downvotes.

Appending data to events

We can return data from our EventEmitter, just by passing it as a parameter. We might like to return a JSON object:

this.liked.emit({
name: 'davey',
age: 47
});

we can return an attribute of the component, or some made up data, even data that has been passed in from outside. Here's that in context:

var CatComponent = ng.core
.Component({
selector: "like",
outputs: ["liked"],
template:
`
<a (click)="handleClick()">
Like?
</a>
`
})
.Class({
constructor: function() {
this.cat = {
name: 'davey',
age: 47
}
this.liked = new ng.core.EventEmitter();
this.handleClick = function() {
this.liked.emit(this.cat);
}
}
});

Escape the Dungeon Exercise

Add a list of items to the location model, like this:

var LocationModel = {
name: "Nondescript Corridor",
description: "It is very dark. To the north you can just make out a faint glimmer of golden light.",
exits: "North",
items: [
{
name: "rusty_sword",
type: "weapon",
damage: 1,
description: "A Rusty old Sword, knocked and well used"
},
{
name: "cheese",
type: "food",
health: 0.5,
description: "A Piece of Mouldy Cheese"
}
]
}

Add buttons to the location component let people pick up items.

When someone picks up an item, have the location component emit an event, firing out the item.

Create an inventory model which is owned by the app component. Have the app component pass the inventory back to the inventory component.

For bonus points, create a status component that says "You picked up the item"!