Forms in Angular
Angular comes with built-in tools to help us with dealing with forms. There are two ways to create forms in Angular:
- template-driven
- reactive
Setup
The FormsModule
needs to be imported into AppModule
.
Template-Driven Approach
This is the easiest approach. It requires the AppModule
to import
FormsModule
.
-
Define our form in HTML within the
<form>
tags. UsengSubmit
event of the form and a reference.The
#f="ngForm"
is a syntax specific to Template-Driven Forms in Angular. We could name it differently than#f
though. -
Mark inputs that matter to you with the
ngModel
directive and name them.In this case, the second input does not matter to us for some reason, so we do not add any attributes to it.
-
Angular will analyze it and produce the JS representation of data in that form. We can retrieve it in handler method:
The NgForm
object contains lots of properties. One of them is value
. In case
of the simple form above, value
would look someting like this:
Among the other included properties are: touched
, dirty
, valid
,
controls
(holds references to controls of the form).
Validation
There are a few directives that we can place on form inputs to have them validated. Examples of such directives are:
required
email
A full list can be found at Angular
Docs - this is a list of all directives
shipped with Angular. The ones with Validator
suffix are for validation.
In case of validation being unsuccessful, the valid
property of NgForm
will
be false
.
The valid
property is also available on each control of the form, so they can
be checked separately.
CSS
Angular adds various classes to inputs in specific states. Examples:
ng-dirty
ng-touched
ng-valid
nv-invalid
We can make use of these classes to apply styling to various states of our form.
HTML 5 Validation
HTML 5 comes with its own validation system. Angular disables it by default. It
can be enabled with ngNativeValidate
attribute on a control.
Default Values
We can provide default, prerendered values for form controls.
The ngModel
is used with square brackets only, it’s not bi-drectional binding
(([ngModel])
).
Grouping Inputs
In bigger forms, we can group inputs. It might be useful also for partial validation - we could mark some group of inputs as invalid.
As you can see above, we can just use div
s with a special directive
ngModelGroup
.
Now, when we access the value
property of that NgForm
, we’d see something
like this:
Also the controls
property of the form will have our basicInfo
listed as a
control. Accessing its valid
property returns validation status for the
grouped inputs.
References
The #f="ngForm"
attribute allowed us to access the form in the Submit button.
We can also access each control on its own, by attributing it with
#mycontrol="ngModel"
. It’s especially useful when we want to check valid
property of a control to conditionally display some helpful text like “username
is required”.
Similarly, we can also mark input groups with ngModelGroup
:
Reactive Approach
The form gets created in TS, we have more control over it. This time, istead of
importing FormsModule
in our AppModule
, we need to import the
ReactiveFormsModule
.
Now, inside of the component wheret the form would reside, we’d create a
property of type FormGroup
:
Now, we need to write HTML code representing the form:
The ngSubmit
event works similarly like in the Template-driven design. This
time we do not have to use reference though, because we have the form variable
in our component already (in the example here it would be myForm
). This
variable will be an object containing the same fields as the NgForm
does.
Inside of value
we will get exactly the same structure of inputs as we defined
in our component.
Validation
We no longer use directives on HTML elements to define validation. We do that in TS code:
CSS
The same way as it’s done with Template-driven forms, Angular assign various CSS
classes to HTML elements based on their state (e.g. ng-touched
).
Custom Validators
We can define custom validators. A validator is just a function that either
returns an object (when invalid) or null
(when valid).
The validator needs to be applied to the control that needs it:
Each control that has some invalid inputs, will have appropriate errors in its
FormControl
’s errors
property. It is an object with keys corresponding to
individual errors. In our custom validator, the key would be called
“hobbyIsInvalid”, becuase that’s the property that we set in the object we
return. The default validators define their own keys. For example, the required
validator just outputs { required: true }
in its logic and that’s the property that would appear in the errors
object.
Asynchronous Validators
In some cases we might want our validators to do some network request, e.g. to check if username is not taken already. Angular supports asynchronous validators for such cases. Here’s how one could looks like:
Asynchronous validators should be passed as a third argument to FormControl
. The second argument is for synchronous validators only.
Accessing Controls
We can access individual controls in HTML with:
Subscriptions
The form and its indiviual control can be subscribed to for:
- value changes
- status changes (e.g. valid, invalid)
Here’s an example for value changes:
Setting Values
We can set all the values of the form with setValue
:
We can also patchValue
in cases when we don’t need to set all of the controls:
Resetting all inputs:
Grouping Inputs
We can nest our inputs in groups:
The HTML would also have to change, it needs to have the group:
Additionally, accessing myForm
needs to reflect the new structure:
Dynamic Controls
With reactive approach, we can add/remove controls dynamically.
The inputs need to be generated in a loop based on the amount of controls being
added to hobbies
. Each control will have a name being an index in the array.
With that code, the value
of our myForm
variable will contain:
username
key with a single value;hobbies
key, being an array of languages that user added.