While developing a web application, or setting dynamic pages and meta tags weâŻneed toâŻdeal with multiple input elements and value types, such limitations could seriously hinder our work â in terms of either data flow control, data validation, or user experience.âŻâŻÂ
This article is an excerpt from the book, ASP.NET Core 5 and Angular, Fourth Edition by Valerio DeâŻSanctisâŻâ A revised edition of a bestseller that includes coverage of the Angular routing module, expanded discussion on the Angular CLI, and detailed instructions for deploying apps on Azure, as well as both Windows and Linux.âŻÂ
Sure, we could easily work around most of the issues by implementing some custom methods within our form-basedâŻcomponents; we could throw some errors such asâŻisValid(),âŻisNumber(), and so on here and there, and then hook them up to our template syntax and show/hide the validation messages with the help of structural directives such asâŻ*ngIf,âŻ*ngFor, and the like. However, it wouldâŻbe a horrible way to address our problem; we didn't choose a feature-rich client-side framework such as Angular to work that way.âŻÂ
Luckily enough, we have no reason to do that since Angular provides us with a couple of alternative strategies to deal with these common form-related scenarios:âŻÂ
Template-Driven FormsâŻÂ
Model-Driven Forms, also known asâŻReactiveâŻFormsâŻÂ
BothâŻare highly coupled with the framework and thus extremely viable; they both belong to theâŻ@angular/formsâŻlibraryâŻandâŻshare a common set of form control classes. However, they also have their own specific sets of features, along with their pros and cons, which could ultimately lead to us choosing one of them.âŻÂ
Let's try to quickly summarize these differences.âŻÂ
Template-Driven FormsâŻÂ
If you've come from AngularJS, there's a high chance that the Template-Driven approach will ring a bell or two. As the name implies, Template-Driven Forms host most of the logic in the template code; working with a Template-Driven Form means:âŻÂ
Building the form in theâŻ.htmlâŻtemplate fileâŻÂ
Binding data to the various input fields using ngModelâŻinstanceâŻÂ
Using a dedicatedâŻngFormâŻobject related to the whole form and containing all the inputs, with each being accessible through theirâŻname.âŻÂ
These things need to be done to perform the required validity checks.âŻTo understand this, here's what a Template-Driven Form looks like:âŻÂ
<formâŻnovalidateâŻautocomplete="off"âŻ#form="ngForm"
(ngSubmit)="onSubmit(form)">Â
<inputâŻtype="text"âŻname="name"âŻvalue=""âŻrequiredâŻÂ
placeholder="Insert the city name..."âŻâŻÂ
[(ngModel)]="city.Name"âŻ#title="ngModel"âŻÂ
/>âŻ
<spanâŻ*ngIf="(name.touchedâŻ||âŻname.dirty) &&âŻâŻÂ
âŻâŻ name.errors?.required">âŻÂ
âŻâŻâŻâŻâŻâŻâŻ Name is a required field: please enter a valid city name.âŻÂ
</span>âŻÂ
<buttonâŻtype="submit"âŻname="btnSubmit"âŻâŻÂ
âŻâŻâŻâŻâŻ [disabled]="form.invalid">âŻÂ
âŻâŻâŻâŻâŻ SubmitâŻÂ
</button>
âŻ
</form>âŻÂ
âŻ
Here, we can access any element, including the form itself, with some convenient aliases â the attributes with theâŻ#âŻsign â and check for their current states to create our own validation workflow. Â
These states are provided by the framework and will change in real-time, depending on various things:âŻtouched, for example, becomesâŻTrueâŻwhen the control has been visited at least once;âŻdirty, which is the opposite ofâŻpristine, means that the control value has changed, and so on. We used bothâŻtouchedâŻandâŻdirtyâŻin the preceding example because we want our validation message to only be shown if the user moves their focus to theâŻ<input name="name">âŻand then goes away, leaving it blank by either deleting its value or not setting it.âŻÂ
These are Template-Driven Forms in a nutshell; now that we've had an overall look at them, let's try to summarize the pros and cons of this approach. Here are the main advantages of Template-Driven Forms:
Template-Driven Forms are very easy to write. We can recycle most of our HTML knowledge (assuming thatâŻwe have any). On top of that, if weâŻcomeâŻfrom AngularJS, we already know how well we can make them work once we've mastered the technique.âŻÂ
They are rather easy to read and understand, at least from an HTML point of view; we have a plain, understandable HTML structure containing all the input fields and validators, one after another. Each element will have a name, a two-way binding with the underlyingâŻngModel, and (possibly) Template-Driven logic built upon aliases that have been hooked to other elements that we can also see, or to the form itself.âŻÂ
Here are their weaknesses:âŻÂ
Template-Driven Forms require a lot of HTML code, which can be rather difficult to maintain and is generally more error-prone than pure TypeScript.âŻÂ
For the same reason,âŻthese forms cannot be unit tested. We have no way to test their validators or to ensure that the logic we implemented will work, other than running an end-to-end test with our browser, which is hardly ideal for complex forms.âŻÂ
Their readability will quickly dropâŻas we add more and more validators and input tags. Keeping all their logic within the template might be fine for small forms, but it does not scale well when dealing with complex data items.
Ultimately, we can say that Template-Driven Forms might be the way to go when we need to build small forms with simple data validation rules, where we can benefit more from their simplicity. On top of that, they are quiteâŻlikeâŻthe typical HTML code we're already used to (assuming thatâŻwe do have a plain HTML development background); we just need to learn how to decorate the standardâŻ<form>âŻandâŻ<input>âŻelements with aliases and throw in some validators handled by structural directives such as the ones we've already seen, and we'll be set in (almost) no time.âŻÂ
For additional information on Template-Driven Forms, we highly recommend that you read the official Angular documentation at:âŻhttps://angular.io/guide/formsâŻÂ
That being said;âŻthe lack of unit testing, the HTML code bloat that they will eventually produce, and the scaling difficulties will eventually lead us toward an alternative approach for any non-trivial form.
Model-Driven/Reactive FormsâŻÂ
The Model-Driven approach was specifically added in Angular 2+ to address the known limitations of Template-Driven Forms. The forms that are implemented with this alternative method are known asâŻModel-Driven FormsâŻor Reactive Forms, which are the exact same thing.âŻÂ
The main difference here is that (almost) nothing happens in the template, which acts as a mere reference to a more complex TypeScript object that gets defined, instantiated, and configured programmatically within the component class: the formâŻmodel.âŻÂ
To understand the overall concept, let's try to rewrite the previous form in a Model-Driven/Reactive way (the relevant parts are highlighted). The outcome of doing this is as follows:âŻ
<formâŻ[formGroup]="form"âŻ(ngSubmit)="onSubmit()">âŻ
<inputâŻformControlName="name"âŻrequiredâŻ/>âŻÂ
<spanâŻ*ngIf="(form.get('name').touchedâŻ||âŻform.get('name').dirty)âŻâŻÂ âŻâŻâŻâŻâŻâŻâŻ
&&âŻform.get('name').errors?.required">âŻÂ âŻâŻâŻâŻâŻâŻâŻ
Name is a required field: please enter a valid city name.âŻÂ
</span>Â
<buttonâŻtype="submit"âŻname="btnSubmit"âŻÂ âŻâŻâŻâŻâŻâŻâŻ
[disabled]="form.invalid">Â
SubmitÂ
</button>âŻÂ
âŻ
</form>Â
As we can see, the amount of required code is much lower.âŻÂ Here's the underlying form model that we will define in the component class file (the relevant parts are highlighted in the following code):âŻÂ
import {âŻFormGroup,âŻFormControlâŻ}âŻfromâŻ'@angular/forms';âŻÂ
classâŻModelFormComponentâŻimplementsâŻOnInitâŻ{âŻÂ
form:âŻFormGroup;âŻâŻÂ
âŻâŻÂ âŻâŻngOnInit() {âŻÂ
âŻâŻâŻâŻthis.formâŻ=âŻnewâŻFormGroup({âŻÂ
âŻâŻâŻâŻâŻâŻ title:âŻnewâŻFormControl()âŻÂ
âŻâŻâŻ });âŻÂ
⯠}âŻÂ
}âŻÂ
Let's try to understand what's happening here:âŻÂ
TheâŻformâŻproperty is an instance ofâŻFormGroupâŻand represents the form itself.âŻÂ
FormGroup, as the name suggests, is a container of form controls sharing the same purpose. As we can see, theâŻformâŻitself acts as aâŻFormGroup, which means that we can nestâŻFormGroupâŻobjects inside otherâŻFormGroupâŻobjects (we didn't do that in our sample, though).âŻÂ
Each data input element in the form template â in the preceding code,âŻnameâŻâ is represented by an instance ofâŻFormControl.âŻÂ
EachâŻFormControlâŻinstance encapsulates the related control's current state, such asâŻvalid,âŻinvalid,âŻtouched, andâŻdirty, including its actual value.âŻÂ
EachâŻFormGroupâŻinstance encapsulates the state of each child control, meaning that it will only be valid if/when all its children are also valid.âŻÂ
Also, note that we have no way of accessing theâŻFormControlsâŻdirectly like we were doing in Template-Driven Forms; weâŻhave toâŻretrieve them usingâŻtheâŻ.get()âŻmethod of the mainâŻFormGroup, which is the form itself.âŻÂ
At first glance, the Model-Driven template doesn't seem too different from the Template-Driven one; we still have aâŻ<form>âŻelement, anâŻ<input>âŻelement hooked to aâŻ<span>âŻvalidator, and aâŻsubmitâŻbutton; on top of that, checking the state of the input elements takes a bigger amount of source code since they have no aliases we can use. What's the real deal, then?Â
To help us visualize the difference, let's look at the following diagrams: here's a schema depicting howâŻTemplate-Driven FormsâŻwork:âŻÂ
[caption id="attachment_72453" align="alignnone" width="690"] Fig 1: Template-Driven Forms schematic[/caption]
By looking at the arrows, we can easily see that, inâŻTemplate-DrivenâŻForms, everything happens in the template; the HTML form elements are directly bound to theâŻDataModelâŻcomponent represented by a property filled with an asynchronous HTML request to theâŻWeb Server, much like we did with our cities and country table. Â
ThatâŻDataModelâŻwill be updated as soon as the user changes something, that is, unless a validator prevents them from doing that. If we think about it, we can easily understand how there isn't a single part of the whole workflow that happens to be under our control; Angular handles everything by itself using the information in the data bindings defined within our template. Â
This is whatâŻTemplate-DrivenâŻactually means: the template is calling the shots.âŻÂ Now, let'sâŻtake a lookâŻat theâŻModel-Driven FormsâŻ(or Reactive Forms) approach:âŻÂ
[caption id="attachment_72454" align="alignnone" width="676"] Fig 2: Model-Driven/Reactive Forms schematic[/caption]
As we can see, the arrows depicting theâŻModel-Driven FormsâŻworkflow tell a whole different story. They show how the data flows between theâŻDataModelâŻcomponent â which we get from theâŻWeb ServerâŻâ and a UI-oriented form model that retains the states and the values of the HTML form (and its children input elements) that are presented to the user. This means that we'll be able to get in-between the data and the form control objects and performâŻa number ofâŻtasks firsthand: push and pull data, detect and react to user changes, implement our own validation logic, perform unit tests, and so on.âŻ
Instead of being superseded by a template that's not under our control, we can track and influence the workflow programmatically, since the form model that calls the shots is also aâŻTypeScriptâŻclass; that's what Model-Driven Forms are about. This also explains why they are also calledâŻReactive FormsâŻâ an explicit reference to the Reactive programming style that favors explicit data handling and change management throughout the workflow.âŻÂ
SummaryâŻâŻÂ
In this article, we focused onâŻthe Angular frameworkâŻandâŻthe two form design models it offers: theâŻTemplate-DrivenâŻapproach, mostly inherited from AngularJS, and theâŻModel-DrivenâŻorâŻReactiveâŻalternative. We took some valuable time to analyze the pros and cons provided byâŻboth, and then weâŻmadeâŻa detailed comparison of the underlying logic and workflow. At the end of the day, we chose theâŻReactiveâŻway, as it gives the developer more control and enforces a more consistent separation of duties between theâŻData ModelâŻand theâŻForm Model.âŻÂ
About the authorâŻÂ
Valerio DeâŻSanctisâŻis a skilled IT professional with 20 years of experience in lead programming, web-based development, and project management using ASP.NET, PHP, Java, and JavaScript-based frameworks. He held senior positions at a range of financial and insurance companies, most recently serving as Chief Technology and Security Officer at a leading IT service provider for top-tier insurance groups. He is an active member of the Stack Exchange Network, providing advice and tips on the Stack Overflow,âŻServerFault, andâŻSuperUserâŻcommunities; he is also a Microsoft Most Valuable Professional (MVP) for Developer Technologies. He's the founder and owner ofâŻRyadelâŻand the author of many best-selling books on back-end and front-end web development.âŻÂ
Â
Â
Read more