Lesson Wednesday

So far we've successfully created an EditTaskComponent and TaskListComponent, including all necessary @Inputs and @Outputs to pass data down and actions up. In this lesson, we'll create our NewTaskComponent with a form to create new Task objects.

Let's generate the component:

$ ng generate component new-task

Now let's add import statements:

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

They're very similar to the the import statements in the EditTaskComponent; however, we won't need Input for this component. While it might be tempting just to copy/paste import statements into components, we should always strive to only import what the component actually needs.

Next we'll place <app-new-task></app-new-task> tags where we'd like to render our NewTaskComponent. We'll place them beneath the <app-edit-task></app-edit-task> tags in the root component's HTML template:

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

As always, we should verify that our home page correctly renders "new-task works!"

Now let's replace that boilerplate with a form for new Tasks that has description and priority fields:

app/new-task/new-task.component.html
<h1>New Task</h1>
<div>
 <label>Enter Task Description:</label>
 <input #newDescription>
</div>
<div>
  <label>Task Priority:</label>
  <select #newPriority>
    <option [value]="1"> Low Priority </option>
    <option [value]="2"> Medium Priority </option>
    <option [value]="3"> High Priority </option>
  </select>
  <button (click)="submitForm(newDescription.value, newPriority.value)">Add</button>
</div>

We have an input field for description and a drop-down menu for priority. We use select fields instead of radio buttons so we can practice using other types of input fields with Angular.

Template Reference Variables

The # symbols in the form above are template reference variables. We can use template reference variables to save our inputs.

By adding the template reference variable #newDescription, we can save a user's input in a newDescription variable. We can then retrieve the user input by calling newDescription.value.

When a user selects an option from the dropdown, this choice will be saved to #newPriority. We can then retrieve the user's input by calling newPriority.value. Let's take a look at the code for our drop down again:

app/new-task/new-task.component.html
...
<label>Task Priority:</label>
<select #newPriority>
  <option [value]="1"> Low Priority </option>
  <option [value]="2"> Medium Priority </option>
  <option [value]="3"> High Priority </option>
</select>
...

Form fields like <select> drop-downs and radio buttons are formatted a little differently than text fields. They have values added like this: [value]="3".

This is because we can't gather user input from radio buttons or drop-downs in the same manner we do with a text box. We need to first determine which option the user selected, then gather the value from that option. This is why we include property bindings that alter the value of the #newPriority template reference variable depending on which option the user selects from the drop-down list.

Template Reference Variables versus Two-Way Data Binding

Template reference variables are generally used in forms that create new objects while two-way data binding is used in forms that edit existing objects.

Why is this? When we fill out a form to create a new object, that object doesn't exist yet. For that reason, we can't bind our data to the object in the same manner we bind edit form data to existing models.

For more information on template reference variables, check out the Angular Documentation.

Creating New Objects with Form Data

Let's take a look at the code for our form's button:

app/new-task/new-task.component.html
...
<button (click)="submitForm(newDescription.value, newPriority.value)">Add</button>
...

The button includes an event binding ((click)) that triggers a submitForm method. The method takes the user input as arguments.

Let's define the method in the component's class declaration:

app/new-task/new-task.component.ts
...
export class NewTaskComponent {
  submitForm(description: string, priority: number) {
    let newTask: Task = new Task(description, priority);
  }
}

Let's add an @Output to pass the submitForm() action up to the root component:

app/new-task/new-task.component.ts
...
export class NewTaskComponent {
  @Output() sendTask = new EventEmitter();

  submitForm(description: string, priority: string) {
    let newTask: Task = new Task(description, parseInt(priority));
    this.sendTask.emit(newTask);
  }
}

Remember that an EventEmitter can only emit one argument at a time; that's why we create a new Task first and send it up as a single argument. This is an appropriate place to create the Task anyway. After all, that's the responsibility of this component!

Note that we pass in priority as a string. Remember that input from a form is always a string, not a number. We then need to parseInt the priority to make it into a number.

Let's update the selector tags in the parent component's HTML template:

app/app.component.html
...
<app-new-task (sendTask)="addTask($event)"></app-new-task>
...

When the child component emits an action via sendTask, it will be passed into the parent component's addTask() method (which we have yet to define). $event is a special variable that holds the argument passed to the child component's EventEmitter (in this case, the newTask).

Let's define this addTask() method in our AppComponent next. It will add the new Task to the array of all Tasks:

app/app.component.ts
...
export class AppComponent {
...
  addTask(newTask: Task) {
    this.masterTaskList.push(newTask);
  }
}

For now, we're just pushing the new Task to our masterTaskList. Next week, we'll learn how to connect Angular to a database.

If we serve our application, we can now use the form to add new Tasks to the list.

Clearing the Form

Let's clear our form's text field after we submit it:

app/new-task/new-task.component.html
...
    <button (click)="submitForm(newDescription.value, newPriority.value); newDescription.value='';">Add</button>
...

An event binding's template statement can have both method calls and property assignments. Here we include a property assignment along with our method call. We set newDescription.value back to an empty string.

When we include multiple actions in an event binding's template statement, we must separate the actions with semicolons.

We should be able to launch our application and use our new button to clear the form after creating an object. Doing the same for our dropdown is more involved and beyond the scope of this lesson. Feel free to set a default value to the dropdown on your own!

In the next lesson, we'll explore pipes that can be used to live-filter data right on the page and put the finishing touches on our to do application.