9

I am having trouble enabling and disabling a form controls in my application. It is probably because the form is being enabled/disabled in an asynchronous context.

Here is how the code is.

user.component.html

<form [formGroup]="form">
    <input type="text" id="name" formControlName="name" />
    <input type="text" id="age" formControlName="age" />
</form>

user.component.ts

ngOnInit() {

     this._route.queryParams.subscribe(params => {
            getUser(params.userId).subscribe(user => {
                 this._buildForm(user);
            })
     });
}

private _buildForm(user){
    this.form = _fb.group({
        name: [{value: user.name, disabled: user.someCondition}, Validators.required],
        age: [{value: user.age, disabled: user.anotherCondition}, Validators.required]
    })
}    

When the first time the user is loaded upon param change, the controls do get enabled/disabled on the basis of their relevant conditions. When the params change subsequently, the state of the controls remain the same although the values are set appropriately.

I have tried different ways to fix this but no help. For example, I have tried the following at the end of the _buildForm method.

this.form.disable() //Doesn't do anything

this.form.controls.name.disable(); //Doesn't help

One thing that does work as expected is the following (but this isn't what is required).

<button (click)="form.disable()" value="Disable Form" />
<button (click)="form.enable()" value="Enable Form" />

What I feel is that the problem is due the fact the _buildForm() is being called from the asynchronous context (subscribe method).

How can I fix this problem?

UPDATE

Please note that the Observable subscription is triggered on the basis of the navigation as below.

this._router.navigate([], {
   relativeTo: this._route,
   queryParams: {userId: 10}
})

UPDATE 2 https://angular-cvf1fp.stackblitz.io

This is an easier way to understand my problem

Kashif Nazar
  • 20,775
  • 5
  • 29
  • 46

4 Answers4

5

You just need enable/disable methods of form control.

Here is stackblitz example. It works perfectly.

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  group;
  isDisabled = true;

  constructor(fb: FormBuilder) {
    this.group = fb.group({
      stuff: fb.control({value: 'stuff', disabled: this.isDisabled})
    });
  }

  toggle() {
    this.isDisabled = !this.isDisabled;

    this.group.controls.stuff[this.isDisabled ? 'enable' : 'disable']();
  }
}
Sharikov Vladislav
  • 7,049
  • 9
  • 50
  • 87
  • Thanks for the answer but the problem is that I am trying to toggle from the subscribe method. If I have a button in the UI, that can easily enable/disable the component. Let me try to modify the example. – Kashif Nazar Apr 26 '18 at 19:07
  • Do you have ChangeDetectionStrategy set up? – Sharikov Vladislav Apr 26 '18 at 19:10
  • No. I don't have – Kashif Nazar Apr 26 '18 at 19:12
  • I updated to the subscribe. It works. Even with ChangeDetectionStrategy.OnPush. – Sharikov Vladislav Apr 26 '18 at 19:13
  • Ok then just provide repro on stackblitz. We will fix it easily then. – Sharikov Vladislav Apr 26 '18 at 19:13
  • My problem is something like this. Hope you understand it now. https://angular-cvf1fp.stackblitz.io Although there is an error in the console but it's not in my application – Kashif Nazar Apr 26 '18 at 19:24
  • Thanks so much for your solution. If you can post it as a separate answer I can mark it as the correct one. – Kashif Nazar Apr 27 '18 at 06:26
  • @KashifNazar did you say that to me? How my example helped you?I checked your example. Somethings strange. Why do you re-create form every 2 seconds? I think it is not the solution. I think you just need enable/disable control like I suggested in my example. My answer is already separated. Seems to me you answered to somebody another. I don't understand what you want now. – Sharikov Vladislav Apr 27 '18 at 06:30
  • I am sorry. The comment was meant for someone else. About the problem, I just used that two seconds interval for the example to demonstrate that the form was being recreated from an Observable. In actual, I am creating the form in an Observable which executes whenever the URL changes. – Kashif Nazar Apr 27 '18 at 18:49
  • So what was the problem? Can you write your own variant as answer? It is interesting. – Sharikov Vladislav Apr 28 '18 at 06:55
2

You're right. The subscription is only fired once, so after submitting the form the user entity does not change anymore. To do it, add for instance an event handler to your form submit.

user.component.html

<form [formGroup]="form" (submit)="_updateForm()">
    <input type="text" id="name" fromControlName="name" />
    <input type="text" id="age" fromControlName="age" />
</form>

user.component.ts

private userId: any;


ngOnInit() {
    this._route.queryParams.subscribe(params => {
        this.userId = params.userId;
        this._updateForm();
    });
}

private _buildForm(user){
    this.form = _fb.group({
        name: [{value: user.name, disabled: user.someCondition}, Validators.required],
        age: [{value: user.age, disabled: user.anotherCondition}, Validators.required]
    });
} 

// The update function has to be public to be called inside your template
public _updateForm(){
    getUser(this.userId).subscribe(user => {
        this._buildForm(user);
    });
}    
Michael W. Czechowski
  • 3,366
  • 2
  • 23
  • 50
  • Thanks for your answer. The subscribe method does fire multiple times whenever there is a change in the params. (userId) – Kashif Nazar Apr 26 '18 at 18:55
2
  • Create the formGroup directly in ngOnit w/controls disabled by default so you're sure it exists w/o worrying about async issues.
  • In this._route.queryParams.subscribe(...) you can use this.form.setValue({//set all control values}) or this.form.patchValue({//set individual controls}) to update the individual controls as needed/once available.
  • Use component instance variables for condition and someOtherCondition (or a user object as a whole if you want to separate your data model from the form's model - they don't have to be the same). Initialize these to "false", and refer to them instead for enabling/disabling the controls in the formGroup creation. You can also update those variables in the same .subscribe() block once the user data is in place, instead of waiting to access user data model directly.

This way your controls and formGroup exist, and are disabled, until the async data is available.

mc01
  • 3,750
  • 19
  • 24
1
<form [formGroup]="form">
    <input type="text" id="name" fromControlName="name" />
    <input type="text" id="age" fromControlName="age" />
</form>

do you see a typo here?fromControlName should be formControlName After that change toggling will work ;)

https://angular-rivxs5.stackblitz.io

Antoniossss
  • 31,590
  • 6
  • 57
  • 99