Lesson Wednesday

Now that we know how to communicate between components by passing data downward with @Inputs and passing actions upwards with @Outputs, let's finish making our child components.

In this lesson, we'll create an EditTaskComponent to manage editing Task objects. In the next lesson, we'll add a NewTaskComponent to create new objects directly through our application instead of relying solely on a hard-coded array.

Our new EditTaskComponent will need to know which Task we are editing, display an edit form to do so, and communicate this new information to its parent AppComponent.

We'll start by generating the component:

$ ng generate component edit-task

Next, let's make sure that we update our import statements:

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

In addition to being able to handle inputs and outputs, this component also needs access to our Task model.

Now let's add <app-edit-task></app-edit-task> selector tags where we'd like to render the component. We'll place the selector tags in at the very end of our root component's HTML template (below where the edit form is currently located):

app/app.component.html
...
<app-edit-task></app-edit-task>

If we serve the application, we should see the boilerplate edit-task works! at the bottom of the home page. Now we can move all the code related to editing a task to the EditTaskComponent's HTML template:

app/edit-task/edit-task.component.html
<div *ngIf="selectedTask">
  <h3>{{selectedTask.description}}</h3>
  <p>Task Complete? {{selectedTask.done}}</p>
  <h3>Edit Task</h3>
  <label>Enter Task Description:</label>
  <input [(ngModel)]="selectedTask.description">
  <label>Enter Task Priority (1-3):</label>
  <br>
  <input type="radio" [(ngModel)]="selectedTask.priority" [value]="1">1 (Low Priority)<br>
  <input type="radio" [(ngModel)]="selectedTask.priority" [value]="2">2 (Medium Priority)<br>
  <input type="radio" [(ngModel)]="selectedTask.priority" [value]="3">3 (High Priority)
  <button (click)="finishedEditing()">Done</button>
</div>

If you refresh the page, the component isn't rendering. However, we've verified that the component should render once we've updated the code (that's what "edit-task works!" is for).

Now that we've separated out the code, the HTML template no longer has access to the selectedTask property and the finishedEditing() method. We'll need to pass Task data down from the parent component so we can define selectedTask. We'll also need to pass the finishedEditing() action up from this component to the parent component.

Passing Data Down to the EditTaskComponent

Let's begin by passing Task data from the root component down to the child component's selectedTask.

First, EditTaskComponent needs an @Input decorator so it can receive data from its parent. (Once again, we've removed ngOnInit because we don't need it.)

app/edit-task/edit-task.component.ts
...
export class EditTaskComponent {
  @Input() childSelectedTask: Task;
}

We've changed the name to childSelectedTask to differentiate it from what will be the parent's selectedTask.

Next, we'll inform the parent AppComponent to pass this data to the child EditTaskComponent by updating the selector tags we just placed in the parent component's HTML template.

app/app.component.html
<app-edit-task [childSelectedTask]="selectedTask"></app-edit-task>

Here we pass the parent's selectedTask data down to the child's childSelectedTask via the child's @Input.

There's one more thing we need to do to get this working: selectedTask needs to be changed to childSelectedTask in edit-task.component.html. Alternatively, you can update the line above to the following:

<app-edit-task [selectedTask]="selectedTask"></app-edit-task>

If you choose to do the latter, you should make sure that you have a clear understanding of the difference between the selectedTasks above!

We should now see our edit form appear when we click any of the Task's "Edit" buttons.

Passing Actions Up from the EditTaskComponent

Our "Done" button isn't hiding the edit form. The EditTaskComponent still needs to send an action up to its parent when clicked.

Let's start by creating an @Output EventEmitter in the child component's class declaration.

app/edit-task/edit-task.component.ts
...

export class EditTaskComponent {
  @Input() childSelectedTask: Task;
  @Output() clickedDone = new EventEmitter();
 }

As always, we'll need to define this method in the component's class declaration:

app/edit-task/edit-task.component.ts
...

export class EditTaskComponent {
  @Input() childSelectedTask: Task;
  @Output() clickedDone = new EventEmitter();

  finishedEditing() {
    this.clickedDone.emit();
  }
}

When the "Done" button is clicked, its event binding will trigger the finishedEditing() method. The method will trigger our EventEmitter and emit() an action to the parent AppComponent.

Let's update the parent component's HTML template so it can receive this action from the child. Specifically, we need to update the app-edit-task selector tags:

app/app.component.html
...
<app-edit-task [childSelectedTask]="selectedTask" (clickedDone)="finishedEditing()"></app-edit-task>
...

When the child component's clickedDone output emits an event, it will trigger the parent component's finishedEditing() method. We already have this method in our root parent component:

app/app.component.ts
...
  finishedEditing() {
    this.selectedTask = null;
  }
...

Our application should now successfully edit tasks and hide the form afterwards. In the next lesson, we'll apply the same concepts to a NewTaskComponent.