angular: formGroup disable()/enable() does not work right after init

I’m submitting a…


[x] Bug report  

Current behavior

I you create a formGroup using formBuilder.group() and call formGroup.disable() or formGroup.enable() right after initialization then it doesn’t work. If you call it using button or setTimeout() then it works.

Expected behavior

Would be nice to be able to disable/enable form right after creation.

Minimal reproduction of the problem with instructions

View: https://ng-form-disable-after-init.stackblitz.io Edit: https://stackblitz.com/edit/ng-form-disable-after-init

Environment

Angular version: 5.2.7

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 66
  • Comments: 42 (3 by maintainers)

Commits related to this issue

Most upvoted comments

Same issue on 8.2.14 image

Version 8 is about to drop…is this bug going to be addressed? I noticed that the severity of this bug is marked as ‘inconvenient’. I wouldn’t say that it’s inconvenient. It essentially breaks the first precept of reactive forms where they are supposed to be synchronous. Somehow a race condition is occurring in ‘synchronous’ code.

I am having the same issue, here is a summary of my setup:

component.ts

ngOnInit() {
  this.disabledGroup = new FormGroup({});
  this.disabledGroup.disable();
}

component.html

<pre>{{ disabledGroup.disabled }}</pre> <!-- prints: false -->

Changing this.disabledGroup.disable() to setTimeout(() => this.disabledGroup.disable()); works as expected.

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

Angular CLI: 6.0.3 Node: 8.11.2 OS: darwin x64 Angular: 6.0.2 … animations, common, compiler, compiler-cli, core, forms … http, language-service, platform-browser … platform-browser-dynamic, router

Package Version

@angular-devkit/architect 0.6.1 @angular-devkit/build-angular 0.6.1 @angular-devkit/build-ng-packagr 0.6.3 @angular-devkit/build-optimizer 0.6.1 @angular-devkit/core 0.6.1 @angular-devkit/schematics 0.6.3 @angular/cli 6.0.3 @ngtools/json-schema 1.1.0 @ngtools/webpack 6.0.1 @schematics/angular 0.6.3 @schematics/update 0.6.3 ng-packagr 3.0.0-rc.5 rxjs 6.1.0 typescript 2.7.2 webpack 4.6.0

another workround for this issue is to call detectChanges() before .disbale()/.enable() operations.

This simple directive works for me:

import { Directive, DoCheck } from "@angular/core";
import { NgControl } from "@angular/forms";

@Directive({
    selector: '[formControlName]'
})
export class FixToggleDisableFormControlDirective implements DoCheck {
    private _enabled: boolean;

    constructor(private _ngControl: NgControl) { 
        this._enabled = this._ngControl.control.enabled;
    }

    ngDoCheck() {
        if (this._ngControl.control.enabled != this._enabled) {
            if (this._ngControl.control.enabled && !this._enabled) {
                this._ngControl.control.enable();
            }
            else if (!this._ngControl.control.enabled && this._enabled) {
                this._ngControl.control.disable();
            }
            this._enabled = this._ngControl.control.enabled; 
        }
    }
}

Tested on Angular@6.0.5

UPDATE: Fix calling enable/disable loop

Same issue on 8.1.3 ! Guys, this issue has been opened almost 2 years ago, come on !

I was having an issue that may be related, thought I’d share for everyone, might help the Angular team. My form component is subscribing to a subject on its parent component, and is supposed to disable itself when the parent is communicating with the server. The subscription happens in ngOnInit, but the actual call to disable() does not happen until the user clicks submit and the client starts communicating with the server, so I thought this issue was unrelated.

if (this.communicating) {
  this.communicating.subscribe((isCommunicating) => {
    if (isCommunicating) {
      this.form.disable()
    } else {
      this.form.enable()
    }
  })
}

After days of frustration, I decided to throw the timeout line into my code, but neglected to remove the old line calling disable(). Bam, it worked! So I go take out the old line, and it no longer worked. That’s when I discovered that you have to double-call disable() to get it to work.

if (this.communicating) {
  this.communicating.subscribe((isCommunicating) => {
    if (isCommunicating) {
      this.form.disable()
      this.form.disable()
    } else {
      this.form.enable()
    }
  })
}

Crazy. This is made even more strange by the fact that I use this method for another form with basically the exact same code, and it works.

I Have the same issue, you can find a minimal working example here

Environment Angular version: 6.1.0

import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { PersonService } from './person.service';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'my-app',
  template: `  
        <form [formGroup]="form">    
        <button class="btn btn-primary" (click)="onFetchPerson()">Click Me</button>                
            <div class="form-group">
                <label>First Name</label>
                <input type="text" class="form-control" formControlName="firstName" />
            </div>
            <div class="form-group">
                <label>Surname</label>
                <input type="text" class="form-control" formControlName="surname" />
            </div>
            <div class="form-group">                
                Disabled:{{form.disabled}}<br>
                Status:{{form.status}}
            </div>
        </form>
    `,
})
export class App implements OnInit {  
  public form: FormGroup;

  constructor(private personService: PersonService) {}

  ngOnInit(): void {
    this.form = new FormGroup({     
      firstName: new FormControl(),
      surname: new FormControl(),
    });
  }

  public onFetchPerson() {
    this.personService.fetchPerson().subscribe(() => {
      this.form = new FormGroup({        
        firstName: new FormControl('John'),
        surname: new FormControl('Doe'),
      });
      this.form.disable();
      console.log(this.form);
    });
  }
}

@Injectable({
  providedIn: 'root',
})
export class PersonService {
  constructor(private http: HttpClient) {}

  public fetchPerson = () => this.http.get('https://randomuser.me/api');
}

@NgModule({
    imports: [BrowserModule, HttpClientModule, ReactiveFormsModule],
    declarations: [App],
    bootstrap: [App],
    providers: [
        PersonService
    ]
})
export class AppModule {}

form-group

Same issue here.

I am as well: I have a class that extends FormGroup, where in the constructor it just calls super({...}) to populate the form, then calls disable().

If I dispatch a new instance of this object to the state, the form in the UI does not render as I’d expect (if the old form was enabled, the fields will still be enabled).

If I wrap the .disable() line in a setTimeout(), then it works as you’d expect.

Also reporting this problem on 8.2

Bump. Same problem with angular 8

Any plan to fix in ReactiveFormModule? this set Timeout solution is annoying since purpose of reactive form is to keep things synchronous.

for the note purpose, this one work for me:

  • using detectChanges() both before and after
  • trigger disable/enable action double times
//.ts
disableForm(customer) {
    this.cd.detectChanges();
    if (customer.isMobile) {
      this.objForm.disable();
      this.objForm.disable();
      this.objForm.updateValueAndValidity();
    } else {
      this.objForm.enable();
      this.objForm.enable();
      this.objForm.updateValueAndValidity();
    }

    setTimeout(() => {
      this.cd.detectChanges();
    }, 100);
  }

@maxime1992

If anyone else has a workaround please post an update

the solution on this comment https://github.com/angular/angular/issues/22556#issuecomment-501670796 works 😃 this is what i am using for more than 2 years 📦

Working example here.

note 1: setTimeout can broke tabbing the form controls in some cases** (accesibility) avoid it if you can.

note 2: Also another option that i never had to use is to don’t rebind a new reference of form but create a reInit methode That reset each control from the current form and clean it’s state/status. You need to tweek the way you create it to simplify the reinit if you want to do it this way…

note 3: detectChanges from the doc https://angular.io/api/core/ChangeDetectorRef

Checks this view and its children. Use in combination with detach to implement local change detection checks.

so it’s pretty fine to use it if you have a form component with it’s children* (use detach / on_push if needed)


@maxime1992 I am debugging angular code base to understand where in the lifecycle it rebind it to evaluate the disabled but to be honest the other issue about ‘readOnly’ is far more important to fix in my opinion so i prefer to check the readonly first 😛 (the one you posted a big text)

Same issue 11.0.7

That’s works for me.

another workround for this issue is to call detectChanges() before .disbale()/.enable() operations.

One way I found that fixes this is if your component is using an @Input, then you can set the disabled() in the ngOnChanges after firstChange.

export class ExampleComponent implements OnInit, OnChanges {

  @Input() myInput: InputClass;
  exFormGroup: FormGroup;

  constructor( private formBuilder: FormBuilder ) { }

  ngOnInit(): void {
    this.exFormGroup = this.formBuilder.group({
      // Your form controls
    });
  }

  ngOnChanges(changes: SimpleChanges): void {
    const change = changes.myInput;
    if (!change.firstChange) {
      this.exFormGroup.disable();
    }
  }

It worked with me after call .enable() inside settimeout setTimeout(() => { this.OTPForm.enable(); }, 1000);

Same Issue here, maybe one day this will be fixed…

Solved this issue with

this.changeDetector.detectChanges(); // use it before using disable/enable
this.form.controls['name'].disable();

Thanks @rcpp0 but I doubt that’s the case.

The issue is happening on a library we built at work: https://github.com/cloudnc/ngx-sub-form

It does work on most of the forms but for some reason, for some of them it doesn’t work 🤷‍♂️. I couldn’t figure out why.

The way the library works to manage sub forms is for every control value accessor we create an internal form group that represents the sub form and here’s how the setDisabledState is handled:

public setDisabledState(shouldDisable: boolean | undefined): void {
  if (shouldDisable) {
    this.formGroup.disable({ emitEvent: false });
  } else {
    this.formGroup.enable({ emitEvent: false });
  }
}

So if working for one, no sign of weird behavior indicating that it shouldn’t work for others here IMO.

After digging into this issue on the library, I found out that within the hook writeValue of the control value accessor, we are doing:

this.formGroup.setValue(transformedValue, {
  emitEvent: false,
});

The fix is actually as simple, weird and unexpected as:

const fgDisabled: boolean = this.formGroup.disabled;

this.formGroup.setValue(transformedValue, {
  emitEvent: false,
});

if (fgDisabled) {
  this.formGroup.disable();
}

If I console.log before and after the setValue the value of this.formGroup.disabled it’s always the same which makes even less sense to me as if there’s an actual bug I thought it would be disabled before and enabled after we update the value. But it’s not case even though the fix seems to point in the opposite direction 🤷‍♂️

I found one solution, without setTimeout and extra directives. Try to put this into your form elements:

[attr.disabled]="formGroup.controls['userName'].disabled"

I’m submitting a…


[x] Bug report  

Current behavior

I you create a formGroup using formBuilder.group() and call formGroup.disable() or formGroup.enable() right after initialization then it doesn’t work. If you call it using button or setTimeout() then it works.

Expected behavior

Would be nice to be able to disable/enable form right after creation.

Minimal reproduction of the problem with instructions

View: https://ng-form-disable-after-init.stackblitz.io Edit: https://stackblitz.com/edit/ng-form-disable-after-init

Environment

Angular version: 5.2.7

Try to bind the inputs with [formControl]=“form.controls[‘controlName’]” insted of formControlName

I have version 8 but still same issue

@lppedd I’ve noticed that when I’m debugging and the app becomes really slow. Add this check in the directive solves my problem

I’ve updated my directive, resolving the enable/disable methods calling infinite loop! 😃