Lesson Thursday

In the last lesson we learned how to pass data down with @Input. In this lesson, we'll learn how to pass actions up with event emitters.

Currently our TaskListComponent can't call editTask() because the method is defined in a different component (our AppComponent). To fix this, we need to pass an action from our TaskListComponent up to its parent AppComponent when the user clicks a task's "edit" button. Our AppComponent can then take the necessary action to handle this request.

Event Emitters

To do this, the child component will need to have an @Output to pass an action outwards and upwards to its parent component. Let's add an @Output to our child TaskList component now. An @Output is known as an EventEmitter in Angular2. As the Angular Documentation states, an EventEmitter is a built-in Angular class that is used to send "events" (also known as actions) between components.

Let's add Output and EventEmitter to our child component's import code:

app/task-list/task-list.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
...

Next we'll add an @Output to the file's class declaration. We'll call it clickSender and set it equal to an EventEmitter object.

app/task-list/task-list.component.ts
...
export class TaskListComponent {
  @Input() childTaskList: Task[];
  @Output() clickSender = new EventEmitter();
 ...
 }

For clarity, let's change the name of the editTask() template statement in our edit button's event binding. This event binding will no longer be responsible for displaying the edit form on its own. Instead, it will use its new @Output to alert its parent that a user has requested to edit a Task. The parent will handle displaying the form. Instead of editTask(), we'll call it editButtonClicked():

app/task-list/task-list.component.html
<ul>
  <li [class]="priorityColor(currentTask)" *ngFor="let currentTask of childTaskList">{{currentTask.description}} <button (click)="editButtonClicked(currentTask)">Edit!</button></li>
</ul>

Now we need to make sure that the editButtonClicked method is defined in our TaskListComponent's class declaration:

app/task-list/task-list.component.ts
...
export class TaskListComponent {
  @Input() childTaskList: Task[];
  @Output() clickSender = new EventEmitter();

  editButtonClicked(taskToEdit: Task) {
    this.clickSender.emit(taskToEdit);
  }
...
}

The editButtonClicked() method simply calls emit() on our @Output. emit() is a built-in EventEmitter method that sends an action upward. By calling emit() on our clickSender and passing in taskToEdit, we're notifying the parent component that a specific Task needs to be edited.

Finally, we need to tell the parent component to expect this event:

app/app.component.html
...
<app-task-list [childTaskList]="masterTaskList" (clickSender)="editTask($event)"></app-task-list>
...
  • Remember, [square brackets] represent input, which is information flowing from the parent component into the child component.

  • (Parentheses) represent output, or actions that are moving out of the child component up to the parent component.

We alert the parent component that an action will emit upwards from <app-task-list> by including (clickSender)="editTask($event)" in the <app-task-list> tags. This instructs the parent component to execute its editTask() method when the clickSender action is sent up from the child component.

$event is simply how you pass an argument upward. Any arguments sent from a child component to a parent component in an upwards action will be housed in $event when it reaches the parent component.

You may only include one argument when passing an action upwards with an EventEmitter @Output(). If we need multiple pieces of data, we must store them in an array or as key-value pairs in an object.

If you refresh the application, the edit form should be working again. We've successfully integrated a second component into our application!

Review

Let's review the process of passing an action up:

app/task-list/task-list.component.html
 <button (click)="editButtonClicked(currentTask)">Edit!</button></li>

1. When the "Edit" button is clicked, its event binding triggers the editButtonClicked() method in the TaskListComponent's class declaration. We also pass along a specific task via the currentTask parameter.

app/task-list/task-list.component.ts
...
  @Output() clickSender = new EventEmitter();

  editButtonClicked(taskToEdit: Task) {
    this.clickSender.emit(taskToEdit);
  }
...

2.\ We've instantiated a new EventEmitter called clickSender in the component's class declaration. The editButtonClicked() in turn triggers the EventEmitter to emit() the taskToEdit up to a parent component.

app/app.component.html
...
<task-list [childTaskList]="masterTaskList" (clickSender)="editTask($event)"></task-list>
...

3. The clickSender event emitter notifies the parent component that the editTask method should be called. clickSender also emits a specific Task and passes it to the parent component via $event.

This may seem like a lot of steps to simply make a method call. However, this allows us to separate our concerns and modularize our application. In smaller applications it might seem like overkill. But here's an analogy that explains why it's important:

Imagine you're a parent component with little kids... you'll want to know what your kids are up to. When they're being unusually quiet in the next room, they might be up to no good. Later, you have no idea why or how the sink is overflowing...until you find a massive ball of gum, marbles and chopsticks in the drain. By then the damage is done and the floor is soaked through. That's the equivalent of a pernicious bug in an application that could potentially blow up in production. If all the child's actions had flowed directly to the parent, the issue would've been discovered and resolved quickly.

Similarly, as a responsible parent component, you might want to be the primary source of data for your kids. Otherwise they may get all their information from other little kids ("The main purpose of chopsticks is to stuff them down drains!") or their own active imaginations ("I'm building a time machine in the sink!"). By passing data down, we can pass along correct information ("Chopsticks are for eating, not for clogging drains"). Once again, we avoid bugs and other issues in our application.

As you separate out components in your application, you'll get plenty of practice with both event emitters and inputs. If you're ever in doubt about when to use them, apply the principles of "data down, actions up."