4 Pitfalls of Angular 14 Typed Forms and How to Fix It

4 Problems of Angular 14 Typed Forms and How to Fix Them

1. We Cannot get Controls Types from Model

You can’t get FormGroup or FormArray from your models’ types. But in real-world forms, bind with models. You should be able to construct controls’ type from model, for example.

You define some model:

interface Model {
a: string;
b: number;
}

Then you define the type:

type Form: SomeMagicType<Model>;

And then you get something like that in our Form:

FormGroup<{
a: FormControl<string>;
b: FormControl<number>;
}>

I know that you may need to manage what you want: FormGroup or FormControl or FormArray in some cases, it is possible to implement, and I will tell you how in the chapter Let’s Fix It.

2. fb.group<Form>(…) Doesn’t Work With Array Syntax

Let’s say that you have form type, but fb.group<Form>(…) doesn’t work with array syntax, for example:

You have some form type:

type Form = FormGroup<{
a: FormControl<string>;
b: FormControl<number>;
}>

Then you pass it to fb.group:

const fb = new FormBuilder()
const form: Form = fb.group<Form['controls']>({
a: ['test'],
b: [42, Validators.required]
})

But if you try it, it won’t work, because fb.group doesn’t support that syntax. So we can’t fix that bug, it is on the side of angular developers

3. You Should Write Useless Boilerplate Code For Forms

If you need some form and need to get form type before it will be init, then you should define types for it and define similar form instance. It’s boilerplate code and if you have many forms it will be very boring, and of course you can make a mistake when you type a lot of boring code. Example:

You have a model:

interface User {
firstName: string;
lastName: string;
birthday: Date;
nickname: string;
}

Then you should define your type:

type UserForm = FormGroup<{
firstName: FormControl<string | null>;
lastName: FormControl<string | null>;
birthday: FormControl<Date | null>;
nickname: FormControl<string | null>;
}>

Then you should define your form:

const userForm: UserForm = fb.group({
firstName: fb.control<string | null>(null),
lastName: fb.control<string | null>(null),
birthday: fb.control<Date | null>(null),
nickname: fb.control<string | null>(null),
})

4. Boilerplate Code Knows Nothing About Models

As you’ve seen from the previous problem, UserForm knows nothing about User interface.

If our model changes you should fix two places (UserForm type and userForm const).

Also, you will see errors only in places where you use setValue or patch or something like that. These are indirect errors and they are not good for developers.

Let’s Fix It

So there’s a need a magic type that would solve our problems. But what does it looks like?

Let’s say we have a complicated model:

interface Model {
a: number;
b: string[];
c: {
d: {
e: number[];
}
}
}

We need to recursively turn our Model to set of FormGroup, FormArray and FormControl.

In TypeScript, we have Mapped Types and types can be recursive. Also we need Conditional Types to manage what we want to infer.

But how do we know what we want to infer? In some cases Model.b should be FormControl instead of FormArray or Model.c should be FormGroup instead of FormControl, how we can describe it?

We need annotation to define what we want in the output type in nesting objects.

Let’s say our type should look like this:

type FormModel<TModel, TAnnotation> = ...

We have some objects, arrays, and primitives, and let’s say Model.b should be FormArray and Model.c.d should be FormGroup. How to write annotations that we can pass to type argument? It should be a type! Ok, what can we use to describe type? We can use objects, arrays, and constants, this is what we need!

So we can write our annotation like this:

type Annotation = { b: 'array', c: { d: 'group' } }

And it is the correct TypeScript type. We can use that.

So we have FormModel<Model, Annotation> type that map our Model to set of controls, using Mapped Types and Conditional Types. We can pass special Annotation that tells our FormModel what control we need to infer. Then we get recursively constructed FormGroup type based on our Model.

This chapter is just a summary of the main idea. You can deep inside in ready solution if you want, and read the docs, tests, and code in my library

GitHub – iamguid/ngx-mf: Bind your model types to angular FormGroup type

Also, you can try it on https://stackblitz.com/edit/angular-ngx-mf

Thank you for reading, have a nice day!

Level Up Coding

Thanks for being a part of our community! Before you go:


4 Pitfalls of Angular 14 Typed Forms and How to Fix It was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding - Medium and was authored by Vlad Lebedev

4 Problems of Angular 14 Typed Forms and How to Fix Them

1. We Cannot get Controls Types from Model

You can’t get FormGroup or FormArray from your models’ types. But in real-world forms, bind with models. You should be able to construct controls’ type from model, for example.

You define some model:

interface Model {
a: string;
b: number;
}

Then you define the type:

type Form: SomeMagicType<Model>;

And then you get something like that in our Form:

FormGroup<{
a: FormControl<string>;
b: FormControl<number>;
}>

I know that you may need to manage what you want: FormGroup or FormControl or FormArray in some cases, it is possible to implement, and I will tell you how in the chapter Let’s Fix It.

2. fb.group<Form>(…) Doesn’t Work With Array Syntax

Let's say that you have form type, but fb.group<Form>(...) doesn’t work with array syntax, for example:

You have some form type:

type Form = FormGroup<{
a: FormControl<string>;
b: FormControl<number>;
}>

Then you pass it to fb.group:

const fb = new FormBuilder()
const form: Form = fb.group<Form['controls']>({
a: ['test'],
b: [42, Validators.required]
})

But if you try it, it won’t work, because fb.group doesn’t support that syntax. So we can’t fix that bug, it is on the side of angular developers

3. You Should Write Useless Boilerplate Code For Forms

If you need some form and need to get form type before it will be init, then you should define types for it and define similar form instance. It’s boilerplate code and if you have many forms it will be very boring, and of course you can make a mistake when you type a lot of boring code. Example:

You have a model:

interface User {
firstName: string;
lastName: string;
birthday: Date;
nickname: string;
}

Then you should define your type:

type UserForm = FormGroup<{
firstName: FormControl<string | null>;
lastName: FormControl<string | null>;
birthday: FormControl<Date | null>;
nickname: FormControl<string | null>;
}>

Then you should define your form:

const userForm: UserForm = fb.group({
firstName: fb.control<string | null>(null),
lastName: fb.control<string | null>(null),
birthday: fb.control<Date | null>(null),
nickname: fb.control<string | null>(null),
})

4. Boilerplate Code Knows Nothing About Models

As you’ve seen from the previous problem, UserForm knows nothing about User interface.

If our model changes you should fix two places (UserForm type and userForm const).

Also, you will see errors only in places where you use setValue or patch or something like that. These are indirect errors and they are not good for developers.

Let’s Fix It

So there’s a need a magic type that would solve our problems. But what does it looks like?

Let’s say we have a complicated model:

interface Model {
a: number;
b: string[];
c: {
d: {
e: number[];
}
}
}

We need to recursively turn our Model to set of FormGroup, FormArray and FormControl.

In TypeScript, we have Mapped Types and types can be recursive. Also we need Conditional Types to manage what we want to infer.

But how do we know what we want to infer? In some cases Model.b should be FormControl instead of FormArray or Model.c should be FormGroup instead of FormControl, how we can describe it?

We need annotation to define what we want in the output type in nesting objects.

Let’s say our type should look like this:

type FormModel<TModel, TAnnotation> = ...

We have some objects, arrays, and primitives, and let’s say Model.b should be FormArray and Model.c.d should be FormGroup. How to write annotations that we can pass to type argument? It should be a type! Ok, what can we use to describe type? We can use objects, arrays, and constants, this is what we need!

So we can write our annotation like this:

type Annotation = { b: 'array', c: { d: 'group' } }

And it is the correct TypeScript type. We can use that.

So we have FormModel<Model, Annotation> type that map our Model to set of controls, using Mapped Types and Conditional Types. We can pass special Annotation that tells our FormModel what control we need to infer. Then we get recursively constructed FormGroup type based on our Model.

This chapter is just a summary of the main idea. You can deep inside in ready solution if you want, and read the docs, tests, and code in my library

GitHub - iamguid/ngx-mf: Bind your model types to angular FormGroup type

Also, you can try it on https://stackblitz.com/edit/angular-ngx-mf

Thank you for reading, have a nice day!

Level Up Coding

Thanks for being a part of our community! Before you go:


4 Pitfalls of Angular 14 Typed Forms and How to Fix It was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding - Medium and was authored by Vlad Lebedev


Print Share Comment Cite Upload Translate Updates
APA

Vlad Lebedev | Sciencx (2022-07-22T20:34:10+00:00) 4 Pitfalls of Angular 14 Typed Forms and How to Fix It. Retrieved from https://www.scien.cx/2022/07/22/4-pitfalls-of-angular-14-typed-forms-and-how-to-fix-it/

MLA
" » 4 Pitfalls of Angular 14 Typed Forms and How to Fix It." Vlad Lebedev | Sciencx - Friday July 22, 2022, https://www.scien.cx/2022/07/22/4-pitfalls-of-angular-14-typed-forms-and-how-to-fix-it/
HARVARD
Vlad Lebedev | Sciencx Friday July 22, 2022 » 4 Pitfalls of Angular 14 Typed Forms and How to Fix It., viewed ,<https://www.scien.cx/2022/07/22/4-pitfalls-of-angular-14-typed-forms-and-how-to-fix-it/>
VANCOUVER
Vlad Lebedev | Sciencx - » 4 Pitfalls of Angular 14 Typed Forms and How to Fix It. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2022/07/22/4-pitfalls-of-angular-14-typed-forms-and-how-to-fix-it/
CHICAGO
" » 4 Pitfalls of Angular 14 Typed Forms and How to Fix It." Vlad Lebedev | Sciencx - Accessed . https://www.scien.cx/2022/07/22/4-pitfalls-of-angular-14-typed-forms-and-how-to-fix-it/
IEEE
" » 4 Pitfalls of Angular 14 Typed Forms and How to Fix It." Vlad Lebedev | Sciencx [Online]. Available: https://www.scien.cx/2022/07/22/4-pitfalls-of-angular-14-typed-forms-and-how-to-fix-it/. [Accessed: ]
rf:citation
» 4 Pitfalls of Angular 14 Typed Forms and How to Fix It | Vlad Lebedev | Sciencx | https://www.scien.cx/2022/07/22/4-pitfalls-of-angular-14-typed-forms-and-how-to-fix-it/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.