Angular2 Forms

Angular2 Status

Angular2 looks like it’s a future for web development. They have put enormous effort in making it interesting for every developer around the globe. Cooperation with Ionic, NativeScript, building with TypeScript from Microsoft and cooperating with them regarding stuff like server pre-rendering and many other things (Steve Sanderson was talking about it during the NDC in London, this year, and also recently published a blog post about it) and now, listening to the guys at ng-conf, makes it feel such a good choice for development framework these days, that you just can’t miss it.

I think that the community using Angular1 will convert quickly, and nobody will start from Angular1 if Angular2 is there, ready to take it. I will, therefore, ignore the existence of Angular1 and when I refer to angular, I will MEAN Angular2.

At the moment of writing this post, Angular2 is already in RC as announced on the ng-conf, the code my post is not taking that into account yet. I wanted to avoid problems during conversion between my version and the RC. I know folks have problems with that from my colleagues at work who are working with Angular2 and have already started the migration process. If this code won’t work in the future builds of Angular, I’m sorry for that 🙂

Angular2 Forms

Angular team has made a huge effort to make Angular2 as much modular and extensible as only possible. I believe they succeeded with it and this is very likely one of the first feelings you have when you put your hands on Angular. Good example here is support for external rendering, which allows Angular to use NativeScript for rendering.

Another one, which is maybe less spectacular, is approach to Forms handling. Depending on how do you like to work, you may do more declarative, or more programmatic, or both.

Declarative

A totally declarative approach, which allows to you very close to “no code”, would allow you to declare stuff in HTML markup. People that used to work with XAML in WPF may have experience very similar to the one proposed by Angular. Let me share an example with you. Let’s suppose what we’re interested in is having a form with title, rating and url of the movie.

Normally, you would want to declare a variable, or model object to keep the values entered by the user synchronized with the DOM elements’ values. Angular lets you do it this way:

 <form #f="ngForm" *ngIf="videoDetails"
 (ngSubmit)="onSubmit(form.value)" >
 <fieldset class="form-group">
 <label for="videoDetailsTitle">Title: </label>
 <input 
 type="text"
 ngControl="videoDetails.title"
 class="form-control" 
 placeholder="Title" 
 id="videoDetailsTitle" 
 /> 
 </fieldset>
 <fieldset class="form-group">
 <label for="videoDetailsRating">Rating: </label>
 <input 
 type="text"
 ngControl="videoDetails.rating" 
 class="form-control" 
 placeholder="Rating" 
 id="videoDetailsRating"
 />
 </fieldset>
 <fieldset class="form-group">
 <label for="videoDetailsUrl">url: </label>
 <input 
 type="text"
 ngControl="videoForm.url" 
 class="form-control" 
 placeholder="URL" 
 id="videoDetailsUrl"
 />
 </fieldset>
 <button type="submit" class="ui button">Submit</button>
 </form>

This #f at the beginning of the form declaration allows me to use it in submit method call binding. Pretty cool, huh? I didn’t event need to bother about creating anything to wire this up for me. It just magically happened.

Programatically

What if you would like to have control on the backing fields’ definition and behavior? Well, that’s easy.

You can go with explicit typing by creating a form manually using the type ‘ControlGroup’ from angular and assigning it to the form markup by using the angular built-in directive called ‘ngFormModel’, like:


 <form [ngFormModel]="videoForm" *ngIf="videoDetails"
 (ngSubmit)="onSubmit(videoForm.value)" >

The thing is now, we have to build the form model (videoForm object) by ourselves. Which is bad on  one hand as we even need to do anything manually, but on the other hand, gives us a lot of flexibility in how we do this, also enabling unit testing and separating this logic from UI.

Having our markup defined this way, we can also do another approach. Instead of instantiating ControlGroup and filling it with controls, we could use ‘FormBuilder’, which sits a bit higher in the API:

export class VideoDetailsComponent {
videoForm: ControlGroup;
 constructor(
fb: FormBuilder,
validationService: VideoValidationService) {
 
this.videoForm = fb.group({
 'title': ['', Validators.required],
 'rating': ['', VideoValidationService.ratingValidator],
 'url': ['', VideoValidationService.urlValidator]
 });
}

So the FormBuilder builds up the whole form for us based on the objects we’re defining in its objects table. If we’d like to do it on a lower level, we could replace FormBuilder with instantiating ControlGroup, providing all the related components manually.

The code I pasted contains important example that angular enables. the ‘Validators.required’ is a built-in validator for the input to become required. But you can define your own validators. In my example I have created a validator for videos URL. This validator will try to match the url using some RegExp. You can combine validators using ‘Validators.compose(validator1, validator2)’.

Value Changes (RxJS)

RxJS support allows you to attach yourself as an observer to the input values changes and react accordingly. Taking my example above, you could write something like:


videoForm.controls['url'].valueChanges.subscribe(
 (value: string) = {
 console.log('url changed to: ' + value);
 });
videoForm.valueChanges.subscribe(
 (value: ControlGroup) = {
 console.log('form changed to: ' + value.value);
 });

So, the form is represented by ‘ControlGroup’  class, which is grouping elements of type ‘Control’. Both types have common ancestor type called ‘AbstractControl’ which offers this ‘valueChanges’ property, which is returning an Observable. This is what you are subscribing to, getting clear notification when the value changes. Please note that you can attach yourself either to the specific input, or the form as a whole. Except some between-inputs synchronization and validation the usages of Rx in such an important part of web apps element (Forms) seem hard to underestimate. This sort of binding could be used to observe the URL and set the video type (youtube, vimeo, channel9) accordingly based on the url format.

Validators

Given the number of input validators coming from Angular out-of-the-box, you can still build on top of that and add new ones, making validation of your forms powerful and flexible.

An example I already mentioned of  built-in validator from Angular is: Validators.required, which (except from-the-code) you can set in markup simply putting ‘required’ in the specific field’s element content. Like that: <input required type=”text” />. If you don’t want to use this declarative approach, you have to attach validators to the controls as I did in my previous listings. When you add control with such validator, the value will be checked against this constraint.

The custom validator implementation should be fairly easy, if it goes about API it is exposing:


ratingValidator(control: Control): { [s: string]: boolean } {
let ratingValue: number = control.value;
if (ratingValue < 0 || ratingValue > 5) {
return { ratingOutOfBounds: true };
}
}

Generally speaking, it returns an object with error type as a key, boolean as a value. The key should represent the type of error with boolean value indicating if this specific error exists in the current content of input field we are validating. There’s quite a few ways as well to indicate in the form, which values didn’t pass the validation along with the global form validation status. For instance, if our ratingValidator returns


{ ratingOutOfBounds: true }

We can then build our markup to reflect all the specific validators’ errors like that:


<div *ngIf=”videoForm.dirty && !videoForm.valid”>
<p *ngIf=”videoForm.find('rating').errors.ratingOutOfBounds”>
Rating must be an integer value between 0 and 5.
<p>
<div>

When you’re defining a control, you may specify a validator and async validator. The constructor of Control class looks like that:


export declare class Control extends AbstractControl {
constructor(value?: any, validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn);

...

}

If you have two (or more) validators, you just do ‘Validators.compose’ or ‘Validators.composeAsync’ and pass the two Validators you want to combine.

All those options look very interesting. I am just wondering what will the community build on top of that. What patterns, and frameworks will be created and how will this simplify working with forms. I am very optimistic about it and can’t wait to implement something complicated using this mechanism.

Stay tuned.

Leave a Reply

Your email address will not be published. Required fields are marked *