Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reactive forms cleared input becomes empty string instead of null #45317

Open
aleripe opened this issue Mar 10, 2022 · 9 comments
Open

Reactive forms cleared input becomes empty string instead of null #45317

aleripe opened this issue Mar 10, 2022 · 9 comments
Labels
area: forms P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
Milestone

Comments

@aleripe
Copy link

aleripe commented Mar 10, 2022

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

forms

Is this a regression?

No

Description

I have created a minimal reproducible sample of the problem here: https://stackblitz.com/edit/angular-ivy-4ltwow

Here you can see the mocked model input (that I receive from server API in the real app) is null at first, but after I edit the value in textbox and clear it, it becomes empty string.

I test this value server side to see if it has changed since before, and empty string is different from null so I have to test the difference from null AND empty string literally everywhere.

I have no alternative other than creating a directive and applying on every text input, but there are literally thousands and if I forget to put only one the problem arises again.

Shouldn't getRawValue return null if the input control is blanked? Wouldn't it be possibile to include an option in the form initialization (or a parameter of getRawValue) to get this desired behavior?

Thanks!

Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/edit/angular-ivy-4ltwow

Please provide the exception or error you saw

No response

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

Angular CLI: 9.0.2 (but I tested up to Angular 13 and it's the same)
Node: 16.14.0
OS: win32 x64

Angular: 9.0.1
... animations, common, compiler, compiler-cli, core, forms       
... language-service, platform-browser, platform-browser-dynamic  
... router
Ivy Workspace: Yes

Anything else?

No response

@JoostK
Copy link
Member

JoostK commented Mar 10, 2022

Here you can see the mocked model input (that I receive from server API in the real app) is null at first, but after I edit the value in textbox and clear it, it becomes empty string.

What do you mean by "clear it"? Just removing the data with backspace? Then it makes sense to me that the resulting data stays the empty string.

Shouldn't getRawValue return null if the input control is blanked? Wouldn't it be possibile to include an option in the form initialization (or a parameter of getRawValue) to get this desired behavior?
This is highly dependable on how projects deal with data; a null value isn't necessarily more desirable than an empty string.

An option might work but seems out-of-place to me. This use-case would likely fit under #3009, which can be achieved today using a custom value accessor. You should be able to write a directive similar to DefaultValueAccessor that provides NG_VALUE_ACCESSOR to override the default DefaultValueAccessor, which can then convert empty strings to null as you desire. This would be more ergnomic with #3009, but that is not yet available.

@aleripe
Copy link
Author

aleripe commented Mar 10, 2022

Yes, I mean using the backspace. It seems to me counter intuitive that upon patching the control value is null, and after having been cleared it becomes an empty string.
I will investigate the DefaultValueAccessor directive. #3009 has been opened since 2015 so I don't think it will happen soon...

Thanks!

@aleripe
Copy link
Author

aleripe commented Mar 10, 2022

I see this method in DefaultValueAccessor:

  writeValue(value: any): void {
    const normalizedValue = value == null ? '' : value;
    this.setProperty('value', normalizedValue);
  }

So shouldn't the value be '' (empty string) since the first patching? I find this behavior inconsistent...

@JoostK
Copy link
Member

JoostK commented Mar 10, 2022

Yeah, writing the value attribute doesn't "bounce back" to the control value.

@ngbot ngbot bot added this to the needsTriage milestone Mar 10, 2022
@yelhouti
Copy link

yelhouti commented Jun 8, 2022

The issue seems to come directly from how the oninput event is handled in html/javascript. the event.target.value of an empty input field is '' (an empty string).
I understand why one would like to change this behavior for all its fields (I a came here for that too) and although I think it's a good default I understand that is (kind of) deviating from the vanilla javascript can be scary and is huge breaking change.

In the meantime, you can just create this directive and "declare + export" it in some CommonModule then import that module in all your modules and it should work.
EDIT:
creating your own directive doesn't work well with other directives because of this code:

valueAccessors.forEach((v: ControlValueAccessor) => {
if (v.constructor === DefaultValueAccessor) {
defaultAccessor = v;
} else if (isBuiltInAccessor(v)) {
if (builtinAccessor && (typeof ngDevMode === 'undefined' || ngDevMode))
_throwError(dir, 'More than one built-in value accessor matches form control with');
builtinAccessor = v;
} else {
if (customAccessor && (typeof ngDevMode === 'undefined' || ngDevMode))
_throwError(dir, 'More than one custom value accessor matches form control with');
customAccessor = v;
}
});

Which means we basically need to "hack" the exiting one, with something like this on the constructor of AppModule for example:

DefaultValueAccessor.prototype.registerOnChange = function (fn: (_: string | null) => void): void {
      this.onChange = (value: string | null) => {
        fn(value === '' ? null : value);
      };
    };

@alxhub alxhub added the P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent label Nov 16, 2022
@ngbot ngbot bot modified the milestones: needsTriage, Backlog Nov 16, 2022
@jarodsmk
Copy link

Glad I came across this -

In my case, I read the information from my strongly-typed FormGroup with .getRawValues() and my <number | null>FormControl instances now have empty string values after being cleared as discussed above. The result of this is down the line, POST-ing my request to my API results in an application/problem+json media type, which my API promptly rejects (being unsupported).

We made a design decision to not use type="number" inputs, as we want a user to focus on a variety of inputs and actually type values in, thus making a 0 value explicit from their side. Worth mentioning this isn't a public-facing system, and our inputs aren't custom components.

@sebastianhaberey
Copy link

@jarodsmk this is exactly what we're experiencing: strongly typed FormControl created as this.fb.control<string | undefined>(undefined). Expecting default value (undefined) when cleared by user. Getting empty string.

@i-am-the-slime
Copy link

You say strongly typed but you don't seem to be using: https://gcanti.github.io/io-ts-types/modules/NonEmptyString.ts.html

@adavs6533
Copy link

adavs6533 commented Jun 4, 2024

I also find the behavior to be inconsistent. At the very least, I would expect some type of option on the form control to treat empty values as null, particularly when the control is initially patched with a null value.

In my particular case, I keep a copy of the initial value around for comparison purposes later. When I call .getRawValue(), it makes the comparisons I have to do w/ the initial value feel pretty hacky and awkward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: forms P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
Projects
None yet
Development

No branches or pull requests

9 participants