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
/>
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.
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>
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() { ... }
}
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));
}
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);
}
}
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>
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>
We can see the outcome by logging the value object of the form.
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:
- Import FormsModule in AppModule
- Use the form tag to wrap all form elements
-
Declare controls: Declare each control by adding
ngModel
and the name of the control -
Expose form object: Set a local reference equal to ngForm in the form tag
#myForm="ngForm"
-
Submit: Submit the form to pass data to the class. You can use event binding
(ngSubmit)="onSubmit(myForm)"
-
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)