A Deep Dive: How Angular 2 Form Models Work
This post was last updated 03/31/2016. Please refer to our Angular 2 eBook for the most recent updates.
Forms are a crucial part of most web applications. Their design can range from simple to complex, with even small forms having a lot of complexity in their validation. Since componentization is extremely important to modern web applications, we need to look at forms differently. For more insight on componentization in Angular 2, see here.
In a component-based application, we want to segment forms into small and reusable pieces of code, stored in Smart and Dumb components. These components are spread across the application, providing several architectural benefits, including flexibility for design changes.
Angular 2 has many directives and model classes that model form capabilities. By attaching directives to DOM elements, we can build up robust validation and data modeling in our applications. Understanding these directives and model classes is the key to having flexible components that provide the same functionality in your application, but behave differently depending on a form’s specific business rules.
This article expects you to know the basics of Angular's forms, and we have a great article about all features to get you started. Check it out!
## Model Representation
The model representation is quite simple: forms are an aggregation of
Control classes. A
Control is one of the three fundamental building blocks used to define forms in Angular 2, however, it cannot be divided into other
ControlGroup can either define all or part of a form: it aggregates many
ControlArray resembles a
ControlGroup, which can also aggregate
Controls. The following diagram illustrates the model:
ControlArray extend from the same abstract class, so they're all an
AbstractControl and reference each other using this same abstract class. This means that it gives you enough flexibility to match any HTML form structure. These building blocks play an important role in the structure; let’s talk more about each one.
By default, a
Control is created for every
<input> or other form component. You can bind an existing
Control to a DOM element, using directives such as
NgControlName. You can also manipulate
Control classes to support form status management, by configuring these classes with validations that are triggered whenever the element value changes.
Validator is a synchronous or asynchronous function referenced by a
Control that will be called whenever its state changes. In the case of multiple
Validators for the same
Control, Angular 2
Validators provide two methods. These methods are
composeAsync, that take as argument a Function and return a Function that runs all.
Under the hood, Angular calls
_executeValidators which maps through the validators array calling each function with the
Control in execution. Finally, _mergeErrors merges the resulting new array. This generates a single map that contains all errors; these are available to be queried. The implementation is slightly different in async validators because they need to wait for the asynchronous executions to be resolved before moving forward. Angular also has a few common built-in utility validators:
Whenever the state needs to be changed, a
Control can call
updateValue to update an element value. In addition, the update triggers the execution of
_calculateStatus, which makes changes are reflected in the parent tree. If you intend to run validations to refresh the form status, you can call
Parents for a control can be a
ControlGroup or a
ControlArray; both act as
Control aggregators. One of the implications of this is that if a single
Control is invalid, all parents are invalid, but siblings are not.
ControlGroup works on a map of
ControlArray works on array of the same. Usually
ControlGroups are used to compose logical groups in the HTML tree, see code example:
In large forms, it might make sense to split sections into groups, so you can query to figure out if a set of child controls is valid. The snippet above attaches an instance of
myForm to the form and binds controls into DOM elements. Note that if the
ngControlGroup directive is applied to the
<div>, Angular understands that
ngControl will get values from
headerGroup. We’ll talk more about that.
One use-case for a
ControlArray is when the form has a list or an unknown number of blocks, and these blocks are replicated through a loop applying certain validations. This happens, because it’s possible to dynamically bind
Controls that are present inside each array element to DOM elements. This way,
Control names will not collide with each other, as they would in a map.
NgFormControl is used to bind a
Control to a DOM element, so Angular knows how a particular data entry can be controlled. My next post in this series will go into more detail on strategies to bind
Controls so stay tuned to the blog for that.
Forms can be more complex than in this example. Another scenario would be if they had a dropdown from which the user selects a type, and then clicks a button to generate a new row with HTML elements. Moreover, the generated set of elements would apply different validations, depending on the type that the user selects. Finally, the code that creates all fundamental
Controls is called in the
Including a reference to itemsControlArray is very handy: doing so makes it unnecessary to traverse the root ControlGroup
addNewItem() dynamically adds more controls into the
ControlArray, and the form validity is updated accordingly.
Each new line has both
Controls as required fields. This means that whenever the users click on the button to add a new line, the form body and global state become invalid. The following Plunker has the full example.
Lastly, Angular provides a @Injectable service called
FormBuilder. It contains a few handy methods (
array), that create instances of form building blocks. You can guess what each one does; however, know that
group receives a map of arrays that simplifies in a short-hand syntax the creation of a
Angular 2 exports a bundle with all form directives; these have very intuitive names and accept different values. It is convenient to know their behaviour, so you can use them properly. Here is the list of basic directives names:
Besides the basic directives, Angular 2 also has
ControlValueAccessor implementation directives. These directives are the bridge between an input control and a DOM Element. The
ControlValueAccessor classes abstract the operations of writing a new value to a DOM element, and have implementations for many data-entry types:
Lastly, a few validators directives are also built in Angular.
Stay tuned for the next post in this series where we'll take a deep dive into directives, examples, and more validations. Remember that you can also refer to this previous post on forms to get yourself up to speed.