DEV Community

Cover image for To Do app with Angular Forms - part 2
Lorenzo Zarantonello for This is Angular

Posted on • Edited on

To Do app with Angular Forms - part 2

We can already see the form data in the console in our application, and we know that there are other ways to pass data in Angular

However, in the context of Angular Forms we want to use FormsModule and FormGroup to "tracks the value and validity state of a group of FormControl instances".

Basic Forms Validation

There are several ways to validate data coming from a form. We will start by using the required attribute in out input element.

<input 
  placeholder="Write a task" 
  ngModel 
  name="userInput" 
  required 
/>
Enter fullscreen mode Exit fullscreen mode

According to MDN, the required attribute, "if present, indicates that the user must specify a value for the input before the owning form can be submitted".

However, in our case, it fails miserably...
If you click on Add, it will always log something.

This is beacause of a default Angular behavior: "By default, Angular disables native HTML form validation by adding the novalidate attribute on the enclosing form tag and uses directives to match these attributes with validator functions in the framework. If you want to use native validation in combination with Angular-based validation, you can re-enable it with the ngNativeValidate directive".

Let's add the ngNativeValidate directive to the form tag and test the app.

Image description

It is not great, but it works fine.

Display items

We need to add some code to our application to display the items added by the user.

Let's start from the template file, app.component.html.

Immediately under the form we add the following code:

// app.component.html

...

<ul>
  <li *ngFor="let item of todoList">{{ item }}</li>
</ul>
Enter fullscreen mode Exit fullscreen mode

What does it do?

I am assuming you are familiar with ul and li tags.

What is more interesting is that ngFor stuff. If you are not familiar with it, you could read the *ngFor syntax like this: For each item inside todoList, create a new <li> and add that item into the newly created <li>.

Where is todoList? We don't have it yet. But as the name suggest, todoList will contain all the items created by the user. Let's than add an array that we call todoList into AppComponent.

// app.component.ts

...
export class AppComponent {
  userInput = '';
  todoList = ['Study Angular', 'Add one elememt', 'Correct typo'];

  onSubmit() { ... }
}
Enter fullscreen mode Exit fullscreen mode

Let's modify onSubmit so that it concatenates the value of the userInput to the todoList array.

// app.component.ts

...
onSubmit() {
    this.todoList = this.todoList.concat(String(form.form.value.userInput));
  }
Enter fullscreen mode Exit fullscreen mode

Image description

General improvements

I will add a few lines of code to achieve the following outcomes:

  • todoList becomes an array of objects
  • each object in todoList contains a unique id, a task, and an optional date
  • it is possible to delete the items in the UI
// app.component.ts

...
export class AppComponent {
  title = 'Ng To Do';
  userInput: string;
  dateInput: string;
  todoList: { id: number; title: string; date?: string }[] = [
    { id: 1, title: 'Study Angular' },
    { id: 2, title: 'Add one elememt' },
    { id: 3, title: 'Correct typo' },
    { id: 4, title: 'Add dates', date: '2022-09-10' },
  ];

  onSubmit(form: NgForm) {
    this.todoList = this.todoList.concat({
      id: Math.random(),
      title: form.form.value.userInput,
      date: form.form.value.date,
    });
    console.log('Submitted', form.form.value);
  }

  onDelete(id: number) {
    this.todoList = this.todoList.filter((item) => item.id !== id);
  }
}
Enter fullscreen mode Exit fullscreen mode

This is not necessarily the best way to handle forms. We will soon start to group our controls.

Notice that todoList has a type: { id: number; title: string; date?: string }[]. The type is an array of objects where each object must includes an id and a title. By appending the question mark to the date property date? we make the property optional.

Inside onSubmit we create a new object with the values from the UI. Then, we concatenate the object to todoList.

The onDelete method takes an id parameter of type number to delete the item associated with that id.

Our template changes as follow

// app.component.html

<h1>{{ title }}</h1>

<form (ngSubmit)="onSubmit(myForm)" #myForm="ngForm" ngNativeValidate>
  <label for="userInput">Add Task</label>
  <input placeholder="Write a task" ngModel name="userInput" required />
  <label for="date">By when</label>
  <input type="date" name="date" ngModel />
  <button type="submit">Add</button>
</form>

<ul>
  <li *ngFor="let item of todoList">
    <button (click)="onDelete(item.id)">X</button>
    {{ item.title }} {{ item.date && 'by' }} {{ item.date ? item.date : '' }}
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

We added a button for each li element. Clicking on the button triggers the onDelete method and passes the id of the element to delete.

The remaining {{ item.date && 'by' }} {{ item.date ? item.date : '' }} adds some code to show different ways to handle data conditionally.

In JavaScript, the logical AND (&&) creates a condition so that it displays the value on the right side of the && only if the condition is true.

The conditional (ternary) operator is another way to handle data conditionally.

Grouping Form Controls

It is worth mentioning that Angular Forms offers the possibility to group controls. Grouping controls might be useful to group information in categories like user profile data, user preferences, etc.

Since our form is small, we add a description input and a label.

We then wrap all the elements related to userInput and taskDescription in a div tag. We add ngModelGroup="taskInfo" to the div to group the elements in it.

// app.component.html

...
<div ngModelGroup="taskInfo">
    <label for="userInput">Add Task</label>
    <input placeholder="Write a task" ngModel name="userInput" required />
    <label for="taskDescription">Description</label>
    <input
      placeholder="Steps to complete the task"
      ngModel
      name="taskDescription"
    />
</div>
Enter fullscreen mode Exit fullscreen mode

We can see the outcome by logging the value object of the form.

Image description

Angular generated a taskInfo field that is another object containing the values of userInput and taskDescription.

You can see a similar field in the controls. That is pretty cool because it has all the properties of the controls in the group. This means that we could apply form checks, like touched or dirty, on the group of elements as a whole.

Conditional to the group being touched you could render some elements.

Wrap Up

To use Angular Forms you need to:

  1. Import FormsModule in AppModule
  2. Use the form tag to wrap all form elements
  3. Declare controls: Declare each control by adding ngModel and the name of the control
  4. Expose form object: Set a local reference equal to ngForm in the form tag #myForm="ngForm"
  5. Submit: Submit the form to pass data to the class. You can use event binding (ngSubmit)="onSubmit(myForm)"
  6. Group controls: You may want to group elements by category. Use ngModelGroup="group-name" to wrap the elements you want to group.

Top comments (0)