This post was last updated 03/31/2016. Please refer to our Angular 2 eBook for the most recent updates.
Introduction
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!
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 Controls. A ControlGroup can either define all or part of a form: it aggregates many Controls. A ControlArray resembles a ControlGroup, which can also aggregate Controls. The following diagram illustrates the model:
ControlGroup and 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 NgFormControl and 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.
A 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 compose and 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: nullValidator, maxLength, minLength, and required.
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 _runValidators and _calculateStatus, which makes changes are reflected in the parent tree. If you intend to run validations to refresh the form status, you can call updateValueAndValidity.
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.
While ControlGroup works on a map of AbstractControl, the 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 constructor:
Including a reference to itemsControlArray is very handy: doing so makes it unnecessary to traverse the root ControlGroup myForm for *ngFor and addnewItem().
Note that 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 (group, control, and 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 ControlGroup.
FORM_DIRECTIVES Prologue
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:
NgControlNameNgControlGroupNgFormControlNgModelNgFormModelNgFormNgControlStatus
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:
DefaultValueAccessorNumberValueAccessorCheckboxControlValueAccessorSelectControlValueAccessor and NgSelectOptionRadioControlValueAccessor
Lastly, a few validators directives are also built in Angular.
RequiredValidatorMinLengthValidatorMaxLengthValidator
What's Next?
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.