angular: Form control value is null when initialized with undefined

Which @angular/* package(s) are the source of the bug?

forms

Is this a regression?

No

Description

When you initialize a FormControl with an undefined value (initial state) the FormControl#value is null. The value is null instead of undefined to be short

Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/edit/angular-ivy-x37dzv

Please provide the exception or error you saw

If you try to do test something like the test will fail


// control was initialized with undefined
expect(control.value).toBe(undefined) // <-- in fact, this is null

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 14.1.0
Node: 16.16.0
Package Manager: npm 8.15.1
OS: win32 x64

Angular: 14.1.0
... animations, cdk, cli, common, compiler, compiler-cli, core 
... forms, material, platform-browser, platform-browser-dynamic
... router

Package                         Version
---------------------------------------------------------      
@angular-devkit/architect       0.1401.0
@angular-devkit/build-angular   14.1.0
@angular-devkit/core            14.1.0
@angular-devkit/schematics      14.1.0
@angular/flex-layout            14.0.0-beta.40
@schematics/angular             14.1.0
rxjs                            7.5.6
typescript                      4.7.4

Anything else?

No response

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 17
  • Comments: 15 (5 by maintainers)

Most upvoted comments

@MikaStark thanks for reporting the issue. It’s a known limitation of the Forms APIs where null is used as a default value in FormControl (see here). Changing this behavior to drop null as the default value would result in a breaking change, see my comment here: https://github.com/angular/angular/issues/40608#issuecomment-769424323. To make it a non-breaking change, we can potentially think of some additional APIs to define what the default value should be (like new FormControl(undefined, {defaultValue: undefined}) on per-control basis or FormControl.useAsDefault(undefined) to set if globally). In any case, this would require extra design work to figure out how that would affect the rest of the Forms logic (there might be more places were the code relies on the null being the default value). We don’t have any ETA, so I’d recommend using a workaround for now.

Given the fact that this issue is reported periodically (#40608, #40978), I’m keeping it open so that we can collect more signal.

Some developers believe there’s no sense in having both, null and undefined, and that it’s more confusing than helpful. Of course, that is a vast topic and should be discussed elsewhere. But there definitely are teams out there who don’t use null at all and the Microsoft TypeScript team is one of them. There’s also a TSLint rule for codebases without null.

The current FormControl API forces null values on everyone and leads to hard-to-read boilerplate code when coercing property types back to T | undefined. So I definitely agree with @MikaStark and would love to see progress on this.

@alxhub maybe I misunderstood what you meant, but regarding your first point, null is and should still be accepted as a value for a nonNullable control. This looks contradictory, but that’s only because it was decided to rename initialValueIsDefault to nonNullable, making things very confusing IMHO.

A nonNullable control can have a null value. nonNullable only means that the reset() method won’t set the value of the control to null, but will instead set it to its initial value (which can be null, BTW). There are many control types (such as an input of type number for example) which should be reset to their initial value when reset() is called, but which must accept null as their value, simply because the user can clear the input, or because we want the input to be initially empty.

Also it’s annoying when interacting with a REST API, where undefined is used for optional fields.

Forcing null forces a translation from and to both ways of doing (null in form must be undefined in network layer).

Also it’s annoying when interacting with a REST API, where undefined is used for optional fields.

Forcing null forces a translation from and to both ways of doing (null in form must be undefined in network layer).

This is exactly what brought me here. Anything using objects or JSON uses undefined for optional fields, which adds an extra step to convert nulls to undefined which must be repeated on every single form. This clutters the code and generally results in a poorer developer experience.

Hi, can you try with this: new FormControl({value: undefined, disabled: true})? from: https://github.com/angular/angular/issues/40608

Please prioritize fixing this. The undefined values should not be converted to null. There are numerous reasons why which have been explained well through these multiple issues opened. It appears this work did not make its way into https://github.com/angular/angular/issues/13721.

We should be able to pass the exact input that we want to output as the value.

Breaking changes are a regular part of software updates. The impact could be minimized by adding a flag, for example: “useNullConversion” (current way), or “useExactValues”. For anyone requiring the current design, use flag A, for everyone else, use flag B, setting flag B to default. The only resolution to the breaking change for existing forms would be to change the flag default from B to A.

@MikaStark thanks for reporting the issue. It’s a known limitation of the Forms APIs where null is used as a default value in FormControl (see here). Changing this behavior to drop null as the default value would result in a breaking change, see my comment here: #40608 (comment). To make it a non-breaking change, we can potentially think of some additional APIs to define what the default value should be (like new FormControl(undefined, {defaultValue: undefined}) on per-control basis or FormControl.useAsDefault(undefined) to set if globally). In any case, this would require extra design work to figure out how that would affect the rest of the Forms logic (there might be more places were the code relies on the null being the default value). We don’t have any ETA, so I’d recommend using a workaround for now.

Given the fact that this issue is reported periodically (#40608, #40978), I’m keeping it open so that we can collect more signal.

+1 There is an important use case for this: using the same Form to either create a new object, or edit an existing one. In the case of editing an existing object, you want to set the (primary key) identity value to the existing ID from the database, but when creating a new one, you need to pass undefined. Null won’t work for that unless you define the identity field as nullable.

Yep, by (2) I mean doing new FormControl(undefined, {nonNullable: true}) would be an error - we’d force you to go through the new FormControl({value: undefined, ...}, ...) signature instead.

@AndrewKushnir I’m thinking that even if we can’t change the main behavior, there are two things which we should consider:

  1. We should consider the interaction with nonNullable: true. Perhaps this shouldn’t be allowed (a null initial value + a non-nullable control), or we should explicitly include null in the return type anyway (that is, ignore nonNullable) if null is specified as the initial value.
  2. We could consider updating the signature types to prevent this call pattern entirely.