Understanding Angular’s Two Form Strategies
Angular offers two distinct approaches for handling user input and validation: Template Driven Forms and Reactive Forms. Each has its own trade-offs and use cases. The Template Driven approach feels familiar to developers coming from AngularJs, while Reactive Forms provide a more programmatic and testable structure.
The choice between them often boils down to the complexity of the form and the need for scalability. For simple data entry, Template Driven Forms offer quick setup with minimal code. For complex, dynamic, or heavily validated forms, Reactive Forms provide better control and flexibility.
Before diving into the specifics of each, it’s helpful to understand the common foundation they share, particularly the state tracking and CSS classes that Angular applies automatically to form controls.
The Angular Forms Module: A Shared Foundation
Both form strategies rely on the core FormsModule and ReactiveFormsModule respectively. They both aim to solve the same fundamental problems: managing form state, validating user input, and providing a smooth user experience with clear error feedback.
These modules abstract away the tedious tasks of tracking which fields have been modified, which are invalid, and when to display error messages. This built-in functionality is a significant advantage, as it ensures consistency across applications and reduces boilerplate code.
Core State Tracking and CSS Classes
Angular automatically tracks the state of each form control and the form as a whole, applying specific CSS classes to reflect their status. These classes are mutually exclusive and can be used for styling purposes.
- Each control starts in an
ng-untouchedstate, indicating the user has not yet interacted with it. - Once the user clicks into a control and then clicks away, it becomes
ng-touched. - If a control’s value meets the validation rules, it gains the
ng-validclass; otherwise, it is marked asng-invalid. - An untouched control with its initial value is considered
ng-pristine. Once the user modifies the value, it becomesng-dirty.
These individual control states propagate to the parent form element. For instance, if a single control within a form is invalid, the entire form element is also marked with the ng-invalid class. Similarly, the form is considered touched if any of its child controls have been touched.
This automatic state management is core to both strategies and provides a powerful mechanism for creating responsive and user-friendly forms.
Part 1: Template Driven Forms Approach
Template Driven Forms mirror the simplicity of AngularJs’ ng-model directive. The logic for validation and data binding is primarily defined in the HTML template, making this approach very intuitive for basic forms.
The key advantage here is the low amount of code required in the component class. However, this convenience comes at the cost of testability and maintainability as forms grow in complexity.
Setting Up Template Driven Forms
The core directives for this approach are not enabled by default. To use them, you must import FormsModule in your application’s root module.
Once imported, Angular automatically applies an ngForm directive to every <form> element in your templates. This directive is the central coordinator, tracking the overall value and validity of the entire form. To opt out of this automatic behavior for specific forms, you can use the ngNoForm attribute.
Creating Your First Template Driven Form
Consider a simple login form with two fields: first name and password. Both fields are mandatory, indicated by the required attribute in the HTML.
The component class for this view is remarkably simple. It contains a member variable to hold the form data and a method to handle submission. All the heavy lifting, such as tracking field states and registering validators, is handled automatically by Angular’s directives.
This is possible because Angular applies a set of implicit directives to the view. Each input field gets its own ngModel directive, which is responsible for tracking the field’s value and validity. This child directive then registers itself with the parent ngForm directive.
The Role of the Submit Button
By default, a submit button within a template-driven form would attempt to perform a native HTTP POST request. This is prevented by the implicit ngSubmit directive, which intercepts the event and invokes a specified component method instead.
In this case, the form’s submit event is bound to the onSubmitTemplateBased() method.
Understanding the ngModel Directive
The ngModel directive provides a powerful and flexible way to connect your form controls to your component’s data model.
It is most commonly used for bi-directional data binding via the [(ngModel)] syntax. This notation ensures that changes in the input field are immediately reflected in the model, and vice-versa. This will look very familiar to developers with AngularJs experience.
However, that full bi-directional binding isn’t always necessary. There are several other patterns you might prefer.
One-Way Data Binding
Instead of keeping the model and view in constant sync, you might prefer to initialize the form with a value and only retrieve the updated value when the user submits the form.
This is achieved with the one-way binding syntax [ngModel]. This initializes the form field with the value from the component, but changes made by the user are not written back to the model until a later event, like a submit.
When the user submits the form, you can then easily access the latest values from the ngForm directive reference and pass them to your handler.
Using ngModel Without Any Data Binding
In scenarios like creation forms, there may not be any initial data to bind. You may solely need the validation and value tracking features.
In such cases, you can simply include the ngModel directive on your input element without using the binding brackets or parenthesis. This adds the control to the form’s tracking system without creating a direct link to the component class.
Evaluating Template Driven Forms
The most obvious benefit of this approach is its simplicity and the speed at which you can set up a form. It feels very familiar to developers with a background in AngularJs and requires very little boilerplate code in the component class.
However, the advantages diminish as forms become more complex. Adding numerous validator tags or implementing cross-field validation logic directly in the template can quickly make it difficult to read and maintain. This can also make it harder for a UI developer to work with the template without risking breaking the business logic.
Furthermore, since the logic is buried within the template and directives, it is nearly impossible to write unit tests for the validation rules. These limitations are what led to the development of the second approach: Reactive Forms.
Note: While these two strategies are distinct, it is important to recognize that they are different mechanisms and should not be mixed carelessly within the same form.
Exploring Angular Reactive Forms
At first glance, a reactive form appears quite similar to its template driven counterpart. However, to construct this type of form, a distinct module must be brought into your application first:
Notice that ReactiveFormsModule has been imported here in place of FormsModule. This action loads the directives for reactive forms, rather than those for template driven forms.
Should you ever find yourself needing the functionality of both, you would proceed by importing both modules simultaneously.
Building Your First Reactive Form
Let's take the earlier form example and reconstruct it, this time using the reactive style:
Several key differences stand out. Initially, a formGroup directive is attached to the entire form, linking it to a component variable identified as form.
Also, observe that the required validator attribute is absent from the form controls. This indicates that the validation logic must reside within the component class, where it becomes significantly easier to test.
Examining the Component Class
The component class for a Reactive Form has a few more moving parts. Let's inspect the component that corresponds to the form above:
It's apparent that the form is essentially a FormGroup, responsible for monitoring both the overall form value and its validity status.
Individual controls can be created separately using the FormControl constructor. The result is a programmatic form model definition, complete with all controls and validation rules. This model is built programmatically within the component class, not in the template.
Leveraging the FormBuilder API
The technique of creating form models by directly calling the FormGroup and FormControl constructors can become quite verbose, particularly as forms grow in size and complexity.
To mitigate this issue, an alternative and equivalent notation is available, utilizing the built-in FormBuilder service:
As shown, rather than invoking the FormGroup and FormControl constructors directly, we've adopted a simplified array syntax to define the form model, resulting in a more concise representation.
Within this array notation, the first element signifies the control's initial value, while the subsequent elements serve as the control's validators. In this instance, both controls are designated as mandatory through the use of the Validators.required built-in validator.
This version of the form is completely analogous to the earlier template driven version, offering the exact same set of features and capabilities.
Advantages of Reactive Forms Over Template Driven Forms
You might be wondering about the tangible benefits here. A significant advantage is immediately visible: the component's template becomes much cleaner and dedicated purely to presentation concerns.
Heavily populating a template with numerous directives to establish business validation rules can quickly become unwieldy for substantial forms. Consequently, it's far tidier to move that logic into the component class.
All business validation logic for each form field has been relocated to the component class, where unit testing becomes a much simpler process.
By centralizing the form model definition within the component, defining the form dynamically becomes trivial, if the need arises. This proves useful, for instance, when the form structure is derived from backend data, enabling the implementation of more advanced scenarios.
Moreover, crafting custom validators with reactive forms is straightforward: one simply defines a function and integrates it into the configuration.
In contrast, template driven forms necessitate the creation of an additional custom directive, which is more involved than writing a simple function, to achieve the same result.
Thus, the reactive forms module facilitates the programmatic definition of the form model, as opposed to doing it declaratively through the view, offering clear benefits compared to template driven forms.
But what is the reasoning behind the term "reactive forms"?
The Observable-based API of Reactive Forms
The designation "Reactive Forms" stems from the fact that both the individual form controls and the form as a whole expose an Observable-based API.
This signifies that both the controls and the entire form can be perceived as a continuous stream of values. This stream can be subscribed to and manipulated using standard RxJs operators.
For instance, you can subscribe to the form's stream of values via the valueChanges Observable:
Here, we are capturing the stream of form values (which updates with each keystroke in an input field) and applying common RxJs operators to it, namely map and filter.
In this specific case, we are transforming the first name to uppercase using map and only permitting valid form values to pass through using filter. This process yields a new stream of valid-only values, to which we can subscribe by supplying a callback that dictates how the UI should respond to the latest valid value.
This observable-based API simplifies the implementation of various advanced features that would otherwise be quite challenging, including:
- Saving a draft of the form in the background as the user gradually completes it.
- Implementing typical desktop functionalities like undo/redo.
Modifying Form Values
There are distinct APIs available for programmatically updating either the entire form or just a selection of fields. For example, let's add a couple of buttons to the reactive form we created earlier:
We can see two buttons designed to update the form value: one for partial updates and another for complete updates. The corresponding component methods would look like this:
It's evident that FormGroup provides two API methods for updating form values:
patchValue(), which performs a partial update. This method does not require values for every field in the form, which is useful when only a few fields need updating.setValue(), which requires all the form's values. When using this method, a value for each form field must be provided; otherwise, an error will be raised to indicate that certain fields are missing.
One might assume that these two APIs could be used to reset the form by passing in empty values for all fields.
This approach would not succeed as expected, since the form's pristine and untouched statuses, along with those of its fields, would not be properly reset.
The Correct Way to Reset a Form
By leveraging the FormGroup API, resetting everything to its pristine and untouched state is straightforward:
Now, let's investigate whether it's possible to combine both form types and evaluate if that is a recommended practice.
Combining Reactive and Template Driven Forms
Under the hood, both Reactive and Template-Driven forms are implemented in a similar fashion: a FormGroup is created for the entire form, and a FormControl instance is generated for each individual control.
The distinction lies in the fact that with Reactive Forms, the form model is explicitly and programmatically defined within the component class. We then connect this model to the template using directives like formGroup or formControlName.
This contrasts with template driven forms, where the same form model composed of FormGroup and FormControl instances is automatically constructed for us by a set of directives applied in the template, such as ngForm and ngModel.
If circumstances required it, it is technically possible to combine and match the two methods of building forms.
However, as a general guideline, it is advisable to choose one method and apply it consistently across the entire application.
Reactive vs. Template Driven Forms: The Right Choice
Reactive Forms are more scalable for handling larger and more intricate forms, and they're better suited for supporting advanced use cases.
They also foster a cleaner separation between business and presentation logic, resulting in HTML templates that are simpler, more readable, and easier to maintain.
With Reactive Forms, implementing custom validation rules—like a password strength check or a multi-field validation constraint—is considerably easier.
This is accomplished by simply writing a function. Conversely, template driven forms require the implementation of a validation directive to invoke the function and bridge it to the template.
In theory, any task can be accomplished with either form type, but numerous common and advanced use cases are significantly easier to implement with reactive forms.
Choosing the appropriate form type
Are you in the process of migrating an AngularJs application to Angular? If so, Template Driven Forms are the perfect fit, as the ngModel supports two-way data binding, just as the AngularJs ng-model directive does.
Apart from that scenario, Reactive Forms are generally the superior option. They are more robust, simpler to use, and encourage a clearer demarcation between the view and the business logic.
For these reasons, Reactive Forms often deliver better performance than Template Driven forms and serve as the more appropriate default choice for new projects.
As previously noted, it's wise to steer clear of using both form types concurrently, as it can become rather confusing.
Nevertheless, it remains possible to use both forms together should an extraordinary need arise.
Let's quickly recap everything we've covered about template driven and reactive forms and discuss the ideal contexts for each.
Concluding Summary
Here are the key differences between Template-Driven and Reactive Forms:
- Template Driven Forms require the
FormsModule, whereas Reactive Forms necessitate theReactiveFormsModule. - Template Driven Forms are built entirely through template directives, while Reactive Forms are defined programmatically within the component class.
- Reactive Forms are the superior default choice for new applications due to their enhanced power and ease of use.
- The Template Driven approach is highly familiar to AngularJs developers and is ideal for a straightforward migration of AngularJs applications.
- The Reactive approach shifts validation logic away from the template, resulting in cleaner templates.
- Reactive Forms are broadly easier to use and support more advanced features through their Observable-based API.
- The choice isn't necessarily exclusive, but for the sake of consistency, it's better to select one approach and use it uniformly, with a preference for Reactive Forms.
The expectation is that this guide proves helpful. Should any questions come to mind, they can be put in the comments section, and a response will follow.
For a detailed exploration of Angular Forms, covering both reactive and template-driven styles, the Angular Forms In Depth course is an excellent resource.
For those who are new to Angular, be sure to look at the Angular for Beginners Course:
Related Angular Articles
If this post was of interest, here are several other popular articles available on this blog:
- Angular Router - How To Build a Navigation Menu with Bootstrap 4 and Nested Routes
- Angular Router - Extended Guided Tour, Avoid Common Pitfalls
- Angular Components - The Fundamentals
- How to run Angular in Production Today
- How to build Angular apps using Observable Data Services - Pitfalls to avoid
- Angular ngFor - Learn all Features including trackBy, why is it not only for Arrays ?
- Angular Universal In Practice - How to build SEO Friendly Single Page Apps with Angular
- How does Angular Change Detection Really Work?
