#1: Template-driven forms

Let's start refactoring our app to use a form for submitting a new To Do item into list.

Use HTML form elements and import FormsModule

First we need to change the template of InputButtonUnitComponent to use HTML form elements by wrapping everything with <form> tag and add the type="submit" attribute to the button. When complete, your template looks something like this:

<form>
  <div class="form-group">
    <input class="todo-input form-control"
           #todoItem
           [value]="title"
           (keyup.enter)="submitValue(todoItem.value); todoItem.value = ''">
  </div>
  <button mat-raised-button
          color="primary"
          type="submit"
          (click)="submitValue(todoItem.value); todoItem.value = ''">
    Save
  </button>
</form>

Angular apps need supporting code from a library to build Angular Forms. Template-driven forms require the FormsModule.

To use the FormsModule add it to the array of imports in the AppModule in app.module.ts. Don't forget to import the library at the top of the app.module.ts file.

2-way data binding

As with almost everything in software development, there are multiple ways to implement a solution for a feature. We want a way to capture user input in the different form fields and bind that input to properties in the component so that we can easily access the information. To help us do so, we will use [(ngModel)] ([()] syntax is called banana in the box). Take a moment to read more about it in the Angular forms documentation.

One thing to note is when using ngModel, each form control needs to have a name attribute.

First add the template variable for the form, then let's use ngModel on the To Do title to bind with our pre-existing title property (the one in .ts file) and to the template reference variable for our form (# syntax).

Try to do it by yourself with help of Angular Forms guide. πŸ’ͺ

Your template after adding those changes should look similar to this:

<form #itemForm="ngForm">
  <div class="form-group">
    <input class="todo-input form-control"
           #todoItem
           [value]="title"
           name="title"
           [(ngModel)]="title"
           (keyup.enter)="submitValue(todoItem.value); todoItem.value = ''">
  </div>
  <button mat-raised-button
          color="primary"
          type="submit"
          (click)="submitValue(todoItem.value); todoItem.value = ''">
    Save
  </button>
</form>

Submitting the value

We now need to handle the user entry and form submission. Let's attack this by adding the bindings to submit the form in the template and handle the submit action in the component.

Add submit bindings to template

You added type="submit" to your button but at the moment it's not used at all. The addition of items works due to old logic on button click and pressing Enter.

Let's remove the click handler and use ngSubmit which is part of ngForm directive instead. The syntax is similar to the click handler, but instead of handling the (click) action by calling a method, we handle the (ngSubmit) action and call a method.

Replace the click handler with (ngSubmit)="onSubmit() in your <form> tag. The form will now know that when user wants to submit the value it needs to run onSubmit() function/method.

The submit handler now expects to call a method called onSubmit(), but instead of writing a new method, we can use submitValue method after we apply a small refactoring change.

Let's change the submit handler to call submitValue() instead. For the input parameter, instead of passing the #todoItem value, we can pass the entire form by refering to the template variable for the form, such as (ngSubmit)="submitValue(itemForm)".

You can also add required directive to the input so it will not allow to submit empty value.

The template is now ready, but we need to refactor .ts file.

Handle the submit action in the component

First of all, when sending entire form to the function, we're passing in a different data type, in this case a data type of NgForm. Update the input parameter name and type to match the new data type we're passing in.

We verify the existence of the input parameter is in the submitValue() method. Now that we are passing in the entire form as the input parameter, we can use the built-in properties of NgForm to check validity. In your if statement, check for NgForm's valid property.

What about value from input? NgForm allows to get values from the form. You've gave the name attribute value title and now you may refer directly to it (newTitleForm.value.title).

Check out your hard work by adding a console.log() to newTitleForm to see the results. πŸŽ‰

The refactored function looks similar to:

  submitValue(newTitleForm: NgForm) {
    if (newTitleForm.valid) {
      this.submitItem.emit(newTitleForm.value.title);
    } else {
      alert('Form is invalid');
    }
  }

Try to use NgForm methods to clear inputs after submitting a value.

See the final result on StackBlitz.

Last updated