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

FormGroup reset() throws error if a control is removed based on a valueChanges subscription #33401

Closed
miguelleite opened this issue Oct 25, 2019 · 7 comments
Assignees
Milestone

Comments

@miguelleite
Copy link

miguelleite commented Oct 25, 2019

🐞 bug report

Affected Package

The issue is caused by package @angular/forms

Is this a regression?

Not sure.

Description

On a FormGroup, we have two FormControl, where the second is shown when, and only when first field has a value.

To achieve this, we're subscribing to the first control valueChanges observable, and if there's no value (and the second control exists), we remove the second one; if there's a value (and the second control doesn't exist yet), we add the second control.

We also have a button that allows us to reset the form to the initial state.

Problem occurs when we reset, where an error is thrown. This happens because the second control is still part of the form group's dictionary of named controls, but the value is undefined (and it tries to call 'reset' of undefined).

🔬 Minimal Reproduction

https://stackblitz.com/edit/angular-issue-repro2-p1nwkd

🔥 Exception or Error

ERROR TypeError: Cannot read property 'reset' of undefined

🌍 Your Environment

Angular Version:

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 8.3.1
Node: 10.15.1
OS: darwin x64
Angular: 8.2.3
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.803.1
@angular-devkit/build-angular     0.803.1
@angular-devkit/build-optimizer   0.803.1
@angular-devkit/build-webpack     0.803.1
@angular-devkit/core              8.3.1
@angular-devkit/schematics        8.3.1
@angular/cdk                      7.3.0
@angular/cli                      8.3.1
@ngtools/webpack                  8.3.1
@schematics/angular               8.3.1
@schematics/update                0.803.1
rxjs                              6.5.2
typescript                        3.5.3
webpack                           4.39.2

Anything else relevant?

No.

@AndrewKushnir
Copy link
Contributor

Hi @miguelleite, thanks for reporting the issue.

It looks like the problem appears because the valueChanges for the name field is fired during the reset and as a result, the age control is removed, but when the logic advances to the next control (age), it's already removed as a part of the processing name field. There is a workaround that you can use to avoid this issue, see Stackblitz example here (I've added the isInResetState flag).

I will keep this issue open to see if we can improve reset function logic to handle these cases better.

Thank you.

@jelbourn jelbourn added P4 A relatively minor issue that is not relevant to core functions and removed severity2: inconvenient labels Oct 1, 2020
@ThomasKruegl
Copy link

This workaround is not what i need, because in my case, i want the control to be removed after the reset. That's why i have that valueChange subscription inthe first place, whenever that value is set to that initial one, the other control should be removed. A simpler "workaround" with that drawback is just using emit: false - which is not what i want in my situation.
I would need to "defer" the logic instead of just skipping it, which is very tedious..

@AndrewKushnir
Copy link
Contributor

AndrewKushnir commented Dec 8, 2020

@ThomasKruegl could you please provide a minimal repro using Stackblitz (and also describe expected vs actual behavior) so that we can have a look? Thank you.

@ThomasKruegl
Copy link

ThomasKruegl commented Dec 9, 2020

Example: https://stackblitz.com/edit/angular-ivy-iuwf7c

Choose "option 1" in the dropdown (shows additional input field), then click the "Rest Form" button -> error in console

I have added your workaround, except one line. If you uncomment that line, your workaround is active. No error in console - but the form control is not removed from the FormGroup on reset. It is visible in the UI in my example because my ngIf checks the control, not the value of the other control. I am aware i could solve the display in the UI by using such a check, or a flag variable, but i want the FormGroup to align with what is displayed.

@AndrewKushnir
Copy link
Contributor

Hi @ThomasKruegl, thanks for the repro.

I believe the solution might be to have more logic in the resetForm function and do a cleanup there, for example as shown here.

Thank you.

@ThomasKruegl
Copy link

Our real form is more complicated. We had a solution like that, but ran into problems because we were basically replicating the add/remove logic in the reset step, which is hard to maintain. Additional problem here is that the form is an Input for child components (working with a stepper).
Our current solution is to recreate the form from scratch on reset - with its own quirks, because there is no central place for adding all the valueChange subscriptions. We now use ngOnChange in the child components to check if the form itself (reference) changed, and readd the component specific logic.

So, yes, there are workarounds, but it would be nice if the simple reset worked ;)

crisbeto added a commit to crisbeto/angular that referenced this issue Jan 17, 2021
…ing reset

When a form is reset, it goes through `_forEachChild` to call `reset` on each of its children.
The problem is that if a control is removed while the loop is running (e.g. by a subscription),
the form will throw an error, because it built up the list of available control before the loop
started.

These changes fix the issue by adding a null check before invoing the callback.

Fixes angular#33401.
@crisbeto crisbeto self-assigned this Jan 17, 2021
AndrewKushnir pushed a commit that referenced this issue Jan 19, 2021
…ing reset (#40462)

When a form is reset, it goes through `_forEachChild` to call `reset` on each of its children.
The problem is that if a control is removed while the loop is running (e.g. by a subscription),
the form will throw an error, because it built up the list of available control before the loop
started.

These changes fix the issue by adding a null check before invoing the callback.

Fixes #33401.

PR Close #40462
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Feb 19, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants