Typed Form groups in Angular - Do Controls in Groups preserve types?

Typed Form Groups in Angular - Are types and nullability of Controls in Groups preserved?

In this article, I would like to explore with you:

  • How to set types and nullability in Form Groups
  • Why is everything in Form Group optional (even when Form Controls are explicitly not nullable)
  • Should we avoid using `getRawValue` — why it can be dangerous?
  • Reducing boilerplate code with Non-Nullable Form Builder

In my previous story about Types of Form Controls, we discussed how can strict types and ‘nonNullable’ flag help us to improve the stability of code (improve checks during compile time) and remove repeating boilerplate code (check if Form Control values are not null). Quick recap:

// simple, but 'if' condition is quite complex
const password = new FormControl(null, { validators: [ Validators.required ] });
if (password.value && (password.value as string).length < 6) {
throw Error('Password is not valid');
}

// ... let`s add type (inferred from default value) -> in 'if' condition it is not required to force type
const password = new FormControl('', { validators: [ Validators.required ] });
if (password.value && password.value.length < 6) {
throw Error('Password is not valid');
}

// ... and set nonNullable to true -> in 'if' we can remove also check for nullability
const password = new FormControl('', { nonNullable: true, validators: [ Validators.required ] });
if (password.value.length < 6) {
throw Error('Password is not valid');
}

But we can also use Form Builder:

const password = this.fb.control('', { nonNullable: true, validators: [ Validators.required ] });
if (password.value.length < 6) {
throw Error('Password is not valid');
}

Types in Form Groups

If Form Control inside Form Group have a type — when we get the value of the whole Form Group, will be type of Controlls preserved?

Let's consider a simple Form Group. Form Control has not type — well, we need to have complex ‘if’ with forcing of types.

const passwordGroup = new FormGroup({
password: [null, [ Validators.required ] ]
});

if (passwordGroup.value.password && (passwordGroup.value.password as string).length > 0) {
throw Error('Password is not valid');
}

But we can apply what we already know about types and nullability of From Control ( … inside Form Group), and ‘if’ is now less complex / better to read.

const passwordGroup = new FormGroup({
password: new FormControl('', { nonNullable: true, validators: [ Validators.required ] }),
});

if (passwordGroup.value.password && passwordGroup.value.password.length > 0) {
throw Error('Password is not valid');
}
Great, value of Form Group can also have strict type — same as Form Control.

Type is preserved: ‘value’ property of Form Group has properties (or better say, structure), where each property has a type as we defined in the constructor of Form Group

In case you are using Form Builder, you can set a type as well:

constructor(private fb: FormBuilder) {
const passwordGroup = this.fb.group({
password: this.fb.control('', { nonNullable: true, validators: [ Validators.required ] })
});

if (passwordGroup.value.password && passwordGroup.value.password.length > 0) {
throw Error('Password is not valid');
}
}

Nullability in Form Groups

Hey, wait! Why do I need to still check if ‘passwordGroup.value.password’ is not null? I set ‘nonNullable’ flag to true, so why it can be null?

Well, the Form Controls of Form Group can be disabled — hence they can be null.

There is a hack called ‘getRawValue’: but it ignores if Form Control (or Group) in Group is disabled. We should be cautious with it!

if (passwordGroup.getRawValue().password.length > 0) {
throw Error('Password is not valid');
}

Non-Nullable Form Builders

Repeat code set same flags, …

To avoid boilerplates, we can use ‘NonNullableFormBuilder’. It works exactly as Form Builder — but all Form Controls are always Non-Nullable (e.g., set with flag ‘nonNullable: true’)

constructor(private nnFb: NonNullableFormBuilder) {
const passwordGroup = this.nnFb.group({
password: this.nnFb.control('', { validators: [ Validators.required ] })
});

if (passwordGroup.value.password && passwordGroup.value.password.length > 0) {
throw Error('Password is not valid');
}
}

… and again, Angular help us to remove repeating code and flags!

Interesting reading:

Template-Driven Form Groups?

The previous article about Typed Forms has an interesting conclusion:

Typed Forms can help us to reduce code (eliminate IF check for nullability, forcing types, etc.) -> Template-Drive Forms can do it even better!

Form Group is no stranger to Template-Driven Form.

<form #f="ngForm" (ngSubmit)="onSubmit(f)">
  • And then define the Model for Form Group(via the interface, with strict types)
  • Before creating an instance of Model, we of course, need to check if some of the Form Controls have not been disabled (otherwise, our Model would need to have all properties optional)

We recommend watching “Prefer Template-Driven Forms” by Ward Bell. Template-driven forms are recommended by ableneo — check our radar.

Thanks to Marek Vodicka for help with this article!

Typed Form groups in Angular - Do Controls in Groups preserve types? was originally published in ableneo Technology on Medium, where people are continuing the conversation by highlighting and responding to this story.