0% found this document useful (0 votes)
80 views32 pages

Angular Forms in Depth. Victor Savkin Is A Co-Founder of - by Victor Savkin - NRWL

The document summarizes how forms are handled in Angular. It discusses the two main form handling modules: FormsModule, which implements template-driven forms, and ReactiveFormsModule, which defines forms in code. The document also covers the form model primitives (FormControl, FormGroup, FormArray), updating form values, disabling controls, asynchronous validation, and composing validators. The goal is to provide an overview of the key aspects of handling forms and user input in Angular.

Uploaded by

jagruti patil
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
80 views32 pages

Angular Forms in Depth. Victor Savkin Is A Co-Founder of - by Victor Savkin - NRWL

The document summarizes how forms are handled in Angular. It discusses the two main form handling modules: FormsModule, which implements template-driven forms, and ReactiveFormsModule, which defines forms in code. The document also covers the form model primitives (FormControl, FormGroup, FormArray), updating form values, disabling controls, asynchronous validation, and composing validators. The goal is to provide an overview of the key aspects of handling forms and user input in Angular.

Uploaded by

jagruti patil
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 32

Angular Forms in Depth

Victor Savkin Follow

Feb 24, 2017 · 10 min read

Victor Savkin is a co-founder of nrwl.io, providing Angular consulting to enterprise teams.


He was previously on the Angular core team at Google, and built the dependency injection,
change detection, forms, and router modules.

Web applications heavily rely on forms. In many ways Angular is so successful because
two-way bindings and ng-model made creating dynamic forms easy.

Although very flexible, the AngularJS 1.x approach has some issues: the data flow in
complex user interactions is hard to understand and debug.
Angular 2.x+ builds up on the ideas from AngularJS 1.x: it preserves the ease of
creating dynamic forms, but avoids the issues making data flow hard to understand.

In this article we will look at how form handling (or input handling) works in Angular.

Read the Series


This is the seventh post in the Essential Angular series, which aims to be a short, but at
the same time, fairly complete overview of the key aspects of Angular.

Essential Angular. Part 1: Compilation

Essential Angular. Part 2: NgModules

Essential Angular. Part 3: Components and Directives

Essential Angular. Part 4: Dependency Injection

Essential Angular. Part 5: Change Detection

Essential Angular. Part 6: Testing

You can also check out the Essential Angular book, which has extra content not
available on this blog.

Example App
Throughout this series I use the same application in the examples. This application is a
list of tech talks that you can filter, watch, and rate.
You can find the source code of the application here.

Two Modules
In AngularJS 1.x, the ng-model directive was baked into the core framework. This is no
longer the case. The @angular/core package doesn’t contain a form-handling library. It
only provides the key primitives we can use to build one.

Of course, making everyone to build their own would not be practical. And that’s why
the Angular team built the @angular/forms package with two modules: FormsModule
and ReactiveFormsModule.

FormsModule implements AngularJS-style form handling. We create a form by


placing directives in the template. We then use data bindings get data in and out of
that form.

ReactiveFormsModule is another take on handling input, where we define a form in


the component class and just bind it to elements in the template. We tend to use
reactive programming to get data in and out of the form, hence the name “reactive”.
At first glance, these two modules seem very different. But once we understand the
underlying mechanisms, we will see how much they have in common. In addition, it will
give us an idea of how to build our own form-handling module if needed.

High-Level Overview
App Model
The app model is an object provided by the application developer. It can be a JSON
object fetched from the server, or some object constructed on the client side. Angular
doesn’t make any assumptions about it.

Form Model
The form model is a UI-independent representation of a form. It consists of three
building blocks: FormControl, FormGroup, and FormArray. We will look at the form
model in detail later in this chapter. Both FormsModule and ReactiveFormsModule use
this model.

Form Directives
These are the directives connecting the form model to the DOM (e.g., NgModel).
FormsModule and ReactiveFormsModule provide different sets of these directives.

DOM
These are ours inputs, checkboxes, and radio buttons.

Form Model
To make form handling less UI-dependent, @angular/forms provides a set of primitives
for modelling forms: FormControl, FormGroup, and FormArray.

Form Control
FormControl is an indivisible part of the form, an atom. It usually corresponds to a
simple UI element, such as an input.

1 const c = new FormControl('Init Value', Validators.required);

form_control.ts
hosted with ❤ by GitHub view raw

A FormControl has a value, status, and a map of errors:

1 expect(c.value).toEqual('Init Value');
2 expect(c.errors).toEqual(null); //null means ‘no errors’
3 expect(c.status).toEqual('VALID');

form_control.ts
hosted with ❤ by GitHub view raw

Form Group
FormGroup is a fixed-size collection of controls, a record.

1 const c = new FormGroup({


2  login: new FormControl(‘’),
3  password: new FormControl(‘’, Validators.required)
4 });

form_group.ts
hosted with ❤ by GitHub view raw

A FormGroup is itself a control, and, as such, has the same methods as FormControl.

1 expect(c.value).toEqual({login: '', password: ''});


2 expect(c.errors).toEqual(null);
3 expect(c.status).toEqual('INVALID');

form_group.ts
hosted with ❤ by GitHub view raw

The value of a group is just an aggregation of the values of its children. Any time the
value of a child control changes, the value of the group will change as well.

In opposite to the value, the form group doesn’t aggregate the errors of its children. It
has its own validators and its own collection of errors.

The status of a group is calculated as follows:

If one of its children is invalid, the group is invalid.

If all of its children are valid, but the group itself has errors, the group is invalid.

If all of its children are valid, and the group has no errors, the group is valid.

Since a form group acts like a control, we can nest form groups in arbitrary ways.

1 const c = new FormGroup({


2  login: new FormControl(''),
3  passwords: new FormGroup({
4   password: new FormControl(''),
5   passwordConfirmation: new FormControl('')
6  })
7 });

nested_form_group.ts
hosted with ❤ by GitHub view raw
Form Array
Whereas FormGroup is a collection of different control types of fixed length,
FormArray is a collection of the same control type of a variable length.

1 const c = new FormArray([


2 new FormControl('one', Validators.required),
3 new FormControl('two', Validators.required)
4 ]);

form_array.ts
hosted with ❤ by GitHub view raw

All the considerations regarding FormGroup’s value, status, and errors apply here as
well.

Updating Form Model

1 const c = new FormGroup({


2 login: new FormControl(''),
3 passwords: new FormGroup({
4 password: new FormControl(''),
5 passwordConfirmation: new FormControl('')
6 }),
7 notes: new FormArray([
8 new FormControl('Buy wine'),
9 new FormControl('Buy cheese')
10 ])
11 });

updating_form.ts
hosted with ❤ by GitHub view raw
There are two ways to update the value of a form: setValue and patchValue:

The setValue method is strict and requires the value to match the structure of the form.

1 c.setValue({
2 login: 'newLogin',
3 password: {
4 password: 'newPassword',
5 passwordConfirmation: 'newPassword'
6 },
7 notes: [
8 'buy wine!!!',
9 'buy cheese!!!'
10 ]
11 });

set_value.ts
hosted with ❤ by GitHub view raw

If we try to set the value of a control that doesn’t exist, or if we exclude the value of a
control, setValue will fail.

The patchValue method works as setValue except that it doesn’t throw when the value is
a superset or a subset of the form.

1 c.patchValue({
2 login: 'newLogin'
2 login: newLogin ,
3 password: {
4 passwordConfirmation: 'newPassword'
5 },
6 somethingEntirelyDifferent: true
7 });

patch_value.ts
hosted with ❤ by GitHub view raw

By default updating a control will update its parents. We can prevent the change from
propagating through the parents by passing onlySelf: true:

1 c.setValue('newLogin', {onlySelf: true});

only_self.ts
hosted with ❤ by GitHub view raw

Disabling Form Model

1 const c = new FormGroup({


2 login: new FormControl(''),
3 password: new FormControl('', Validators.required),
4 acceptTerms: new FormControl(null)
5 });
6 const acceptTerms = c.get('acceptTerms');
7 acceptTerms.disable();
8
9 expect(c.value).toEqual({login: '', password: ''});

disable_form.ts
hosted with ❤ by GitHub view raw
This exempts acceptTerms validation checks and excludes it from the aggregate value of
any parent. Its status gets set to DISABLED.

Async Validations
The required validator we have used throughout the chapter is synchronous. The
moment we set value, the moment the control goes into the VALID or INVALID state.
Some validations, however, have to be asynchronous. A good example is the uniqueness
of the login.

1 const login = new FormControl('', null, uniqLoginValidator); // null is sync validator


2 login.setValue('john66');

async_validator.ts
hosted with ❤ by GitHub view raw

This will set the status of the control and its parents to PENDING. And, once the promise
returned by uniqLoginValidator resolves, the status will be set to either INVALID or
VALID.

In addition to declarative validators, we can always set the errors on the control
imperatively.

1 login.setErrors({uniq: false});

set_errors.ts
hosted with ❤ by GitHub view raw

Composing Validators
A validator is just a function that takes a control and returns a map of errors.
1 class Validators {
2 static required(control): {[key: string]: any} {
3 return isEmptyInputValue(control.value) ? {'required': true} : null;
4 }
5 }

validators.ts
hosted with ❤ by GitHub view raw

The value doesn’t have to be a boolean. We can provide extra information to create a
more meaningful error message.

1 class Validators {
2 static minLength(minLength: number): ValidatorFn {
3 return (control): {[key: string]: any} => {
4 const length: number = control.value ? control.value.length : 0;
5 return length < minLength ?
6 {'minlength': {'requiredLength': minLength, 'actualLength': length}} :
7 null;
8 };
9 }
10 }

validators.ts
hosted with ❤ by GitHub view raw
Since the return value of a validator is a map, and not a single value, it’s easy to compose
multiple validators into one.

1 const between3And30: ValidatorFn = Validators.compose([


2 Validators.minLength(3),
3 Validators.maxLength(30)
4 ]);

validators.ts
hosted with ❤ by GitHub view raw

The provided compose function will execute all the validators and merge the errors. We
can, of course, implement our own compose function that, for instance, will execute
validators until the first failure.

Listening to Changes
Any time a control updates, it will emit the value.

1 const login = new FormControl('');


2 const password = new FormControl('', Validators.required);
3 const c = new FormGroup({login, password});
4
5 login.valueChanges.subscribe(c => console.log("login value updates to", c));
6 login.statusChanges.subscribe(c => console.log("login status updates to", c));
7
8 password.valueChanges.subscribe(c => console.log("password value updates to", c));
9 password.statusChanges.subscribe(c => console.log("password status updates to", c));
10
11 c.valueChanges.subscribe(c => console.log("form value updates to", c));
12 c.statusChanges.subscribe(c => console.log("form status updates to", c));
13
14 login.setValue('newLogin');
15 password.setValue('newPassword');
16
17 // will print:
18 // login value updates to 'newLogin'
19 // form value updates to {login: 'newLogin', password: ''}
20 // password value updates to 'newPassword'
20 // password value updates to newPassword
21 // password status updates to 'VALID'
22 // form value updates to {login: 'newLogin', password: 'newPassword'}
23 // form status updates to 'VALID'

listening_to_changes.ts
hosted with ❤ by GitHub view raw

As you can see the value of the form has been updated twice. We can prevent this by
setting the value on the form itself.

1 const c = new FormGroup({login, password});


2 c.setValue({login: 'newLogin', password: 'newPassword'});
3
4 // will print:
5 // login value updates to 'newLogin'
6 // password value updates to 'newPassword'
7 // password status updates to 'VALID'
8 // form value updates to {login: 'newLogin', password: 'newPassword'}
9 // form status updates to 'VALID'

listening_to_changes.ts
hosted with ❤ by GitHub view raw
We can also prevent the events from being emitted altogether by passing emitEvent: false.

Power of RxJS
Since valueChanges and statusChanges are RxJS observables, we can use the rich set of
RxJS combinators to implement powerful user interactions in a just a few lines of code.

Why Form Model?


The form model is a UI-independent way to represent user input comprising simple
controls (FormControl) and their combinations (FormGroup and FormArray), where:

Each control has a value.

Each control has a status.

Each control has validators and errors.

Each control can be disabled.

Each control emits events.

Having this model has the following advantages:

Form handling is a complex problem. Splitting it into UI-independent and UI-


dependent parts makes them easier to manage.

We can test form handling without rendering UI.

Having the form model makes reactive forms possible (see below).
Form Directives
Abstractly describing input is all well and good, but at some point we will need to
connect it to the UI.
@angular/forms provides two modules that do that: FormsModule and
ReactiveFormsModule.
ReactiveFormsModule

ReactiveFormsModule is simpler to understand and explain than FormsModule. That’s


why I will cover it first.

1 @Component({
2 selector: 'filters-cmp',
3 template: `
4 <div [formGroup]="filters">
5 <input formControlName="title" placeholder="Title">
6 <input formControlName="speaker" placeholder="Speaker">
7 <input type="checkbox" formControlName="highRating">High Rating
8 </div>
9 `
10 })
11 export class FiltersCmp {
12 filters = new FormGroup({
13 speaker: new FormControl(),
14 title: new FormControl(),
15 highRating: new FormControl(false),
16 });
17
18 constructor(app: App) {
19 this.filters.valueChanges.debounceTime(200).subscribe((value) => {
20 app.applyFilters(value);
21 });
22 }
23 }
24
25 @NgModule({
26 imports: [
27 ReactiveFormsModule
28 ],
29 declarations: [
30 FiltersCmp
31 ]
32 })
33 class AppModule {}

reactive_forms.ts
hosted with ❤ by GitHub view raw
There are a few things here to note.

First, we import ReactiveFormsModule, which provides, among others, the formGroup


and formControlName directives.

Second, we manually construct a form model.

1 filters = new FormGroup({


2 speaker: new FormControl(),
3 title: new FormControl(),
4 highRating: new FormControl(false),
5 });

form_group_reactive.ts
hosted with ❤ by GitHub view raw

Third, we bind the constructed form to the div using formGroup. Then we use
formControlName to bind the title, speaker, and highRating to the three inputs. The name
suffix indicates that we need to pass the name of a field of the containing group.

When using ReactiveFormsModule we are responsible for creating the form model.
We use the directives merely to bind the form to elements in the UI.

Then we use the constructed form model directly to synchronize it with the client model
or trigger events. And we often do it by subscribing to the valueChanges and
statusChanges observables.

FormsModule
FormsModule implements AngularJS-style forms.

1 @Component({
2 selector: 'filters-cmp',
3 template: `
4 <form (submit)="applyFilters()">
5 <input [(ngModel)]="speaker" name="speaker" placeholder="Speaker">
6 <input [(ngModel)]="title" name="title" placeholder="Title">
7 <input [(ngModel)]="highRating" name="highRating" type="checkbox">High Rating
8 </form>
9 `
10 })
11 export class FiltersCmp {
12 speaker: string;
13 title: string;
14 highRating: boolean;
15
16 constructor(private app: App) {}
17
18 applyFilters() {
19 this.app.applyFiltes({speaker, title, highRating});
20 }
21 }
22
23 @NgModule({
24 imports: [
25 FormsModule
26 ],
27 declarations: [
28 FiltersCmp
29 ]
30 })
31 class AppModule {}

forms.ts
hosted with ❤ by GitHub view raw

This is similar to AngularJS 1.x. We use the [()] syntax to bind the speaker, title, and
highRating properties of the filters component to the three inputs. We then invoke the
applyFilters method when the user submits the form.

Even though it’s not seen in the example, the following form group still gets created:

1 filters = new FormGroup({


2 speaker: new FormControl(),
3 title: new FormControl(),
4 highRating: new FormControl(false),
5 });

form_model.ts
hosted with ❤ by GitHub view raw

The difference is that it does not get created by the application developer, but by the
NgModel and NgForm directives.

The NgForm directive gets instantiated at <form (submit)=”applyFilters()”>. This


directive creates an empty FormGroup.

The NgModel directive gets instantiated at <input [(ngModel)]=”speaker”


name=”speaker” placeholder=”Speaker”>. This directive creates a `FormControl`
and adds it to the FormGroup created by the encompassing NgForm.

How is it possible?

If you have read the chapter on change detection carefully, you probably wonder how
this works. Shouldn’t the following fail?

1 @Component({
2 selector: 'filters-cmp',
3 template: `
4 {{f.controls.speaker == null}}
5
6 <form #f="ngForm" (submit)="applyFilters()">
7 <input ngModel name="speaker" placeholder="Speaker">
8 <input ngModel name="title" placeholder="Title">
9 <input ngModel name="highRating" type="checkbox">High Rating
10 </form>
11 `
12 })
13 export class FiltersCmp {
14 //...
15 }

h d t ti f il t h t d ith ❤ b GitH b i
change_detection_failure.ts
hosted with ❤ by GitHub view raw

If NgModel and NgForm were implemented naively, the {{f.controls.speaker == null}}


binding would evaluate to true the first time, when the group is empty, and will evaluate
to false once NgModels add their form controls to the group. This change from true to
false will happen within a change detection run, which, in opposite to AngularJS 1.x, is
disallowed in Angular 2.x+. The value of a binding can change only between change
detection runs.

To make it work, NgModel doesn’t add a form control synchronously — it does it in


a microtask. In the example above, the three ngModels will schedule three microtasks to
add the speaker, title, and highRating controls.

During the first change detection run, the form will always be empty and
{{f.controls.speaker == null}} will always evaluate to true. Then, after the three
microtasks, Angular will run change detection again, and {{f.controls.speaker == null}}
will evaluate to false.

This is how we can preserve all the guarantees of Angular 2.x+ and still make the API
feel AngularJS-like.

Accessing Form Model When Using FormsModule


We can still access the form model by either querying for the NgForm directive or by
referencing it in the template.

1 @Component({
2 selector: 'filters-cmp',
3 template: `
4 <form (submit)="applyFilters()">
5 <input ngModel name="speaker" placeholder="Speaker">
6 <input ngModel name="title" placeholder="Title">
7 <input ngModel name="highRating" type="checkbox">High Rating
8 </form>
9 `
10 })
11 export class FiltersCmp {
12 @ViewChild(NgForm) ngForm: NgForm;
13
14 constructor(private app: App) {}
15
16 applyFilters() {
17 this.app.applyFiltes(ngForm.form.value);
18 }
19 }

view_child.ts
hosted with ❤ by GitHub view raw
1 @Component({
2 selector: 'filters-cmp',
3 template: `
4 <form #f="ngForm" (submit)="applyFilters(f.form)">
5 <input ngModel name="speaker" placeholder="Speaker">
6 <input ngModel name="title" placeholder="Title">
7 <input ngModel name="highRating" type="checkbox">High Rating
8 </form>
9 `
10 })
11 export class FiltersCmp {
12 constructor(private app: App) {}
13
14 applyFilters(g: FormGroup) {
15 this.app.applyFiltes(g.value);
16 }
17 }

forms-var.ts
hosted with ❤ by GitHub view raw

Once we get the model, we can interact with it imperatively or subscribe to its
observables.
The DOM

The ngModel, ngControlName and other form directives bind the form model to UI
elements, which are often native to the platform (e.g., <input>), but they do not have
to be. For instance, NgModel can be applied to an Angular component.
1 <md-input [(ngModel)]="speaker" name="speaker" placeholder="Speaker">

md-input-ng-model.ts
hosted with ❤ by GitHub view raw

A ControlValueAccessor is a directive that acts like a adapter connecting a UI


element to NgModel. It knows how to read and write to the native UI-element.

The @angular/forms package comes with value accessors for all built-in UI elements
(input, textarea, etc). But if we want to apply an NgModel to a custom element or an
Angular component, we will have to provide a value accessor for it ourselves.

1 @Component({
2 selector: 'custom-input',
3 providers: [
4 {
5 provide: NG_VALUE_ACCESSOR,
6 useExisting: forwardRef(() => CustomInputCmp),
7 multi: true
8 }
9 ]
10 })
11 class CustomInputCmp implements ControlValueAccessor {
12 //...
13 }

custom-input.ts
hosted with ❤ by GitHub view raw
Wrapping Up
Form handling is a complex problem. One of the main reasons AngularJS got so
successful is that two-way bindings and ng-model provided a good solution for it.

But there were some downsides, mainly complex forms built with ng-model made the
data flow of the application hard to follow and debug. Angular 2.x+ builds up on the
ideas from Angular 1, but avoids its problems.

NgModel and friends are no longer part of the core framework. The @angular/core
package only contains the primitives we can use to build a form-handling module.
Instead, Angular has a separate package — @angular/forms— that comes with
FormsModule and ReactiveFormsModule that provide two different styles of handling
user input.

Both the modules depend on the form model consisting of FormControl, FormGroup, and
FormArray. Having this UI-independent model, we can model and test input handling
without rendering any components.

Finally, @angular/forms comes with a set of directives to handle build-in UI elements


(such as <input>), but we can provide our own.

Essential Angular Book


This article is based on the Essential Angular book, which you can find here
https://leanpub.com/essential_angular. If you enjoy the article, check out the book!

Victor Savkin is a co-founder of Nrwl. We help companies develop like


Google since 2016. We provide consulting, engineering and tools.
If you liked this, click the 👏 below so other people will see this here on Medium. Follow
@victorsavkin to read more about monorepos, Nx, Angular, and React.

Sign up for Nrwl News


By Nrwl

Nrwl's latest news from the team. We'll share what we're working on and share tips and techniques for
software development.  Take a look.

Get this newsletter

Angularjs JavaScript Angular2 Software Development Front End Development

About Write Help Legal

Get the Medium app

You might also like