Reference from [Forms Overview](Forms • Overview • Angular) Stack blitz playground
Template-driven forms
Use directives ngForm, ngModel to implicit create form controls on template. This approach is a quick and simple way to implement forms, the model that binds to a form control is directly updated by ngModel.
Rely on directives in the template to create and manipulate the underlying object model. They are useful for adding a simple form to an app, such as an email list signup form. They're straightforward to add to an app, but they don't scale as well as reactive forms. If you have very basic form requirements and logic that can be managed solely in the template, template-driven forms could be a good fit.|
Usage
In component file
public personName: string;
public personAge: string;
public submit() {
console.log(this.personName, this.personAge);
}In template file
<form (ngSubmit)="submit()">
<label for="name">Name </label>
<input id="name" name="name" type="text" [(ngModel)]="personName"/>
<label for="age">Age </label>
<input id="age" name="age" type="text" [(ngModel)]="personAge"/>
<button type="submit">Submit</button>
</formFrom a very simple setup above, we now have a fully functional form that can store user input.
The directive ngModel is wrapped as [()] banana in a box, which is two-way binding. Two-way binding means the model can input data into the directive ngModel and ngModel can output data into model.
Data flow
This section will explain more about ngModel two-way binding works.
View to model
- User input value to the
<input>element <input>element emit value change event to the ControlValueAccessor- The ControlValueAccessor triggers the
setValue()method on theFormControlinstance. - The
FormControlinstance emits the new value through thevalueChangesobservable. - The ControlValueAccessor also calls the
NgModel.viewToModelUpdate()method which emits anngModelChangeevent. ngModelChangeevent is an output emitted to the model, in this case it ispersonName.- In this scenario, we are using the output binding
(ngModel)=personName
Model to view
- Component model
personNameis updated programatically - Change detection begins.
- During change detection, the
ngOnChangeslifecycle hook is called on theNgModeldirective instance because the value of one of its inputs has changed. - The
ngOnChanges()method queues an async task to set the value for the internalFormControlinstance. - Change detection completes.
- On the next tick, the task to set the
FormControlinstance value is executed. - The
FormControlinstance emits the latest value through thevalueChangesobservable. - TheControlValueAccessor updates the form input element in the view with the latest value.
- In this scenario, we are using input binding
[ngModel]=personName
Listen for value changes
It is not stated in the docs, means this approach should not be handled in a reactive way. However, we still can do the traditional change event
<label for="name">Name </label>
<input id="name" name="name" type="text" [(ngModel)]="personName" (change)="handleNameChange($event)"/>DOM change event can emit values whenever user input changes, we can access this value by passing $event into the method. As stated above, this kind of implementation is not recommended as this affect performance by triggering multiple changeDetections. Additionally, we don't have much control over the flow of events. Meaning, we have to implement a custom debounce and throttle function to mitigate the number of events fired to avoid performance loss.
Another method is to @ViewChild the <form> element. This way, we can access to the ngForm directive and it's FormGroup. A FormGroup containing multiple FormControls that are bound with ngModel. We can listen to value changes of these form controls. But doing this, we are basically using ReactiveForms !
@ViewChild('form') ngForm!: NgForm;
ngOnInit(): void {
setTimeout(() => {
this.ngForm.form.controls['name'].valueChanges.subscribe((value) => {
console.log('Name changes', value);
});
});Reactive forms
Compared to Template driven form, Reactive forms are more scalable because this approach doesn't rely on templates. This approach, explicitly declares FormGroup or FormControl in the component file. This way, we can programmatically handle the form validations, update values, reset form state, ...etc. It is scalable because, everything is handled on the component side, the template can even render the form controls dynamically. So if the form needs update, we just need to update on the component side and leave the template untouched. Aside from that, Reactive forms are typed, so that we can improve readability and maintainability. Type errors are checked at compile time, provide autocomplete for IDEs, ...etc.
Usage
public personNameControl = new FormControl();
// OR
public formGroup = new FormGroup({
name: new FormControl(),
age: new FormControl()
});
ngOnInit(): void {
this.personNameControl.valueChanges.subscribe((value) => {
console.log('Name changes', value);
});
this.formGroup.valueChanges((values) => {
console.log(values.name, values.age);
});
}Data flow
The flow of data in Reactive forms is more straightforward than Template-driven forms
- The user types a value into the input element, in this case the favorite color Blue.
- The form input element emits an "input" event with the latest value.
- The
ControlValueAccessorlistening for events on the form input element immediately relays the new value to theFormControlinstance. - The
FormControlinstance emits the new value through thevalueChangesobservable. - Any subscribers to the
valueChangesobservable receive the new value.
Key differences
| Reactive | Template-driven | |
|---|---|---|
| Setup of form model | Explicit, created in component class | Implicit, created by directives |
| Data model | Structured and immutable | Unstructured and mutable |
| Data flow | Synchronous | Asynchronous |
| Form validation | Functions | Directives |
Form validation
Template driven forms
We can use validation directive on the template
<input id="name" name="name" type="text" [(ngModel)]="personName" required/>Reactive forms
We can define validations at the form declaration or add dynamically.
actorForm: FormGroup = new FormGroup({
name: new FormControl(this.actor.name, [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i), // Custom validator
]),
});
ngOnInit(): void {
// Add validator using addValidators method
setTimeout(() => {
this.actorForm.controls.name.addValidators([...])
}, 2000);
}