Many of frontend applications applications are basically just huge forms, spanning multiple tabs and dialogs and with non-trivial validation business logic.
Every form-intensive application has to provide answers for the following problems:
import {NgModule} from "@angular/core";
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
import {BrowserModule} from "@angular/platform-browser";
import {FormsModule} from "@angular/forms";
@Component({...})
export class App { }
@NgModule({
declarations: [App],
imports: [BrowserModule, FormsModule],
bootstrap: [App]
})
export class AppModule {}
platformBrowserDynamic().bootstrapModule(AppModule);
<section class="sample-app-content">
<h1>Template-driven Form Example:</h1>
<form #f="ngForm" (ngSubmit)="onSubmitTemplateBased()">
<p>
<label>First Name:</label>
<input type="text"
[(ngModel)]="user.firstName" required>
</p>
<p>
<label>Password:</label>
<input type="password"
[(ngModel)]="user.password" required>
</p>
<p>
<button type="submit" [disabled]="!f.valid">Submit</button>
</p>
</form>
</section>
Angular is actually tracking three form field states for us and applying the following CSS classes to both the form and its controls:
These CSS state classes are very useful for styling form error states.
@Component({
selector: "template-driven-form",
templateUrl: 'template-driven-form.html'
})
export class TemplateDrivenForm {
user: Object = {};
onSubmitTemplateBased() {
console.log(this.vm);
}
}
Not much to see here! We only have a declaration for a view model object user, and an event handler used by ngSubmit.
The way that this works, is that there is a set of implicitly defined form directives that are being applied to the view. Angular will automatically apply a form-level directive to the form in a transparent way, creating a FormGroup and linking it to the form.
ngNoForm
required | maxlength
The presence of [(ngModel)] will also create a bidirectional binding between the form and the user model, so in the end there is not much more to do at the level of the controller.
<p>
<label>First Name:</label>
<input type="text" [ngModel]="user.firstName" required>
</p>
<p>
<label>Password:</label>
<input type="password" [ngModel]="user.password" required>
</p>
user = {
firstName: 'John',
password: 'test'
};
<p>
<label>First Name:</label>
<input type="text" name="firstName" required ngModel>
</p>
<p>
<label>Password:</label>
<input type="password" name="password" required ngModel>
</p>
Disadvantages:
Advantages: simplicity
Bi-directional binding is a solution that promotes mutability
Each form has a state that can be updated by many different interactions
import {NgModule} from "@angular/core";
import {ReactiveFormsModule} from "@angular/forms";
@Component({...})
export class App { }
@NgModule({
declarations: [App],
imports: [BrowserModule, ReactiveFormsModule],
bootstrap: [App]
})
export class AppModule {}
<section class="sample-app-content">
<h1>Model-based Form Example:</h1>
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<p>
<label>First Name:</label>
<input type="text" formControlName="firstName">
</p>
<p>
<label>Password:</label>
<input type="password" formControlName="password">
</p>
<p>
<button type="submit" [disabled]="!form.valid">Submit</button>
</p>
</form>
</section>
import { FormGroup, FormControl, Validators, FormBuilder }
from '@angular/forms';
@Component({
selector: "model-driven-form",
templateUrl: 'model-driven-form.html'
})
export class ModelDrivenForm {
form: FormGroup;
firstName = new FormControl("", Validators.required);
constructor(fb: FormBuilder) {
this.form = fb.group({
"firstName": this.firstName,
"password":["", Validators.required]
});
}
onSubmitModelBased() {
console.log("model-based form submitted");
console.log(this.form);
}
}
FormGroup and FormControl code
FormBuilder code
We can now unit test the form validation logic
The FormGroup and
FormControl classes provide an API that allows us to build UIs using a completely different programming style known as Functional Reactive Programming.
this.form.valueChanges
.map((value) => {
value.firstName = value.firstName.toUpperCase();
return value;
})
.filter((value) => this.form.valid)
.subscribe((value) => {
console.log("Model Driven Form valid value: vm = ",JSON.stringify(value));
});
reset() {
this.form.reset();
}
If by some reason we would need to, we could mix and match the two ways of building forms, for example:
But in general its better to choose one of the two ways of doing forms, and using it consistently throughout the application.
Are you migrating an Angular 1 application into Angular 2? That is the ideal scenario for using Template Driven Forms.
Or are you building a new application from scratch ? Reactive forms are a good default choice, because more complex validation logic is actually simpler to implement using them.
This blog post gives a high level overview of how Angular 2 will better enable Functional Reactive Programming techniques.
If you are interested in learning about how to build components in Angular 2, check also The fundamentals of Angular 2 components.