Site icon Ryadel

Forms in Angular - Template-Driven vs Model-Driven or Reactive Forms

Forms in Angular - Template-Driven vs Model-Driven or Reactive Forms

As we most certainly know, HTML forms are one of the most important and delicate aspects of any business application. Nowadays, forms are used to fulfill almost any task involving user-submitted data, such as registering or logging in to a website, issuing a payment, reserving a hotel room, ordering a product, performing and retrieving search results, and more. If we were asked to define a form from a developer's perspective, we would come out with the statement "a form is an UI-based interface that allows authorized users to enter data that will be sent to a server for processing".

The moment we accept this definition, two additional considerations should come into mind:

  • Each form should provide a data-entry experience good enough to efficiently guide our users through the expected workflow, otherwise they won't be able to properly use it.
  • Each form, as long as it brings potentially unsecure data to the server, can have a major security impact in terms of data integrity, data security and system security, unless the developer possess the required know-how to adopt and implement the appropriate countermeasures.

Forms in Angular

Since it's version 2 major rewrite, Angular provides us a couple of alternative strategies to deal with these common form-related scenarios: Template-Driven Forms and Model-Driven (or Reactive) Forms.

Both of them are thigh-coupled with the framework and thus extremely viable; they both belong to the @angular/forms library and also share a common set of form control classes. However, they also have their own specific sets of features, along with their pros and cons, that can ultimately lead us to choose one of them: in this post we'll do our best to summarize these differences.

Template-Driven forms

If you come from AngularJS, there's an 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 forms means to build the form in the .html template file, to data-bind the various input fields to a ngModel instance, and use a dedicated  ngForm object, related to the whole form and containing all the inputs, each one of them being accessible through their name, to perform the required validity checks.

To better understand it, here's how a template-driven form looks:

As we can see, 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, as opposite of pristine, means that the control value has changed, and so on. We used both of them in the preceding example, because we want our validation message to be shown only if the user moves his focus to the <input name="qName"> and then goes away, leaving it blank by either deleting its value or not setting it.

These are template-driven forms in a nutshell; let's try to summarize the pros and cons of this approach.

The pros

  • 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 came from AngularJS, we already know how well we can make them work once we've mastered the technique.
  • They are also 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) a template-driven logic built upon aliases hooked to other elements that we can also see, or to the form itself.

The cons

  • 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 doesn't 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 similar to the template code we already have in place; we can replace our container DIVs with <form> elements, decorate our input fields with aliases, throw in some validators handled by *ngIf statements, and we will be set in (almost) no time.

However, the lack of unit testing and the HTML code bloat that they will eventually produce will eventually lead us toward the alternative approach.

For additional information on Template-Driven forms, we highly recommend you to read the official Angular documentation at https://angular.io/guide/forms.

Model-Driven / Reactive forms

The Model-Driven approach was specifically added in Angular 2+ to address the known limitations of the Template-Driven Forms; the forms implemented with this alternative method are known as Model-Driven Forms or Reactive Forms.

The main difference here is that (almost) nothing happens in the template, which acts as a mere reference of a TypeScript object--the form model--that gets instantiated and configured programmatically within the component class.

To better understand the overall concept, let's try to rewrite the previous form in a Model-Driven / Reactive way (the relevant parts are highlighted):

Here's the underlying form model that we will define in the component class file (the relevant parts are highlighted):

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 FormGroups (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 be valid only if/when all its children are also valid.

Also note that we have no way to access 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 much 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, as they have no aliases we can use. Where's the real deal?

To help us visualize the difference, let's look at the following diagrams; here's a scheme depicting how Template-Driven Forms work:

Forms in Angular - Template-Driven vs Model-Driven or Reactive Forms

If we look 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 component data model represented by a property filled with an asynchronous HTML request to the web server. That data model will get updated as soon as the user changes something, unless some 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 found in the data bindings defined within our template. This is what Template-Driven actually means.

Let's now take a look at the Model-Driven Forms (or Reactive Forms) approach:

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 component data model--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) presented to the user. This means that we'll be able to get in the middle 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 superseeded 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 is 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.

For additional information on Model-Driven / Reactive Forms, we highly recommend you to read the official Angular documentation at https://angular.io/guide/reactive-forms.
This article is part of the ASP.NET Core 2 and Angular 5 book, available as paperback, e-book and as a 26-lessons video-course. Promo Code: ASPCA50 to get it with a 50% discount! The book's latest edition, updated to ASP.NET Core 5 and Angular 11, is available here.
Exit mobile version