2

I have a nested reactive form in angular.

The child has 2 properties and the second one is either enabled or disabled based on the value of the first.

The parent form has an edit button that enables/disables fields through this.parentForm.enable().

My problem is this enables the second child field no matter what and doesn't use the logic in the child to determine if it should be enabled or disabled.

What is the suggested approach for reactive forms with enable/disable logic in the children? I tried using [disabled] but it didn't work and I received warnings that I shouldn't do this.

Here's a stackblitz with a sample problem.

When edit is clicked it should only enable the last input if Yes is checked. It should be disabled if No is checked.

Ashley
  • 422
  • 6
  • 18

3 Answers3

4

so add a function like this to your controller:

 private setChildState() { 
    // this function gets the ctrl to enable/disable and does so based on the value
    const v = this.parentForm.get('typeDetails').get('identity').value;
    let ctrl = this.parentForm.get('typeDetails').get('identityname');
    if (v === 'yes') {
      ctrl.enable();
    } else {
      ctrl.disable();
    }
 }

this is to set your child form enable / disable state, then just add these few lines:

this in your ngOnViewInit()

// this listens to value changes and updates form state
this.parentForm.get('typeDetails').get('identity').valueChanges.subscribe(v => {
  this.setChildState();
});

and then this in your edit function:

this.setChildState();

fixed blitz: https://stackblitz.com/edit/angular-bwfn35?file=src/app/app.component.ts

EDIT:

if you want to keep this all (mostly) in the child, change your child to this:

  ngOnInit() {
    this.identifyForm.get('identity').valueChanges.subscribe(v => {
      this.setState();
    })
  }

  setState() {
    const v = this.identifyForm.get('identity').value;
    let ctrl = this.identifyForm.get('identityname');
    if (v === 'yes') {
      ctrl.enable();
    } else {
      ctrl.disable();
    }
  }

then add this to your edit function in the parent:

this.childComponent.setState();
bryan60
  • 28,215
  • 4
  • 48
  • 65
  • This does work but it leaks the logic of the child into the parent. This child will be used multiple places with the same enable/disable logic each time, so I'd have to duplicate the setChildState in each parent. Is is possible to do it without having the child logic in each parent? – Ashley Jul 26 '19 at 21:27
  • In my case, Bryne's solution helped me to resolve my issue. – Aleksei Korkoza Jul 06 '21 at 14:07
0

Move the Radio Group code to your parent component and subscribe to the changes

based on the value yes or no

enable and disble child component form control

filed stackblitz: https://stackblitz.com/edit/angular-kszjwj

Vamsi Ambati
  • 321
  • 1
  • 4
  • 9
  • This leaks the child's validation logic into the parent. Becomes a real problem when the child is used in multiple forms. – Ashley Jul 29 '19 at 21:08
0

Another aproach is improve the enabled directive of this stackoverflow answer

We are going to improve the directive to allow disabled not only the control else all the controls inside a form.

@Directive({
  selector: '[disableControl]'
})
export class DisableControlDirective {
  @Input() set disableControl(condition: boolean) {
    if (this.ngControl) { //if is an ngControl
      if (condition)
        this.ngControl.control.disable();
      else
        this.ngControl.control.enable();
    }
    if (this.controls) { //if is a formGroup, we ask about the inners controls
      this.controls.forEach((x: any) => {
        let control:boolean=false;
        if (this.innerControl) //we check if the control has
                               //his own DisableControlDirective
          control=(this.innerControl.find(inner=>x==inner.ngControl)!=null)

        if (!control) { //if it has not a own DisabledControlDirective
          if (condition)
            x.control.disable();
          else
            x.control.enable()
        }
      })
    }
  }
  @ContentChildren(NgControl) controls: QueryList<NgControl>
  @ContentChildren(DisableControlDirective)
             innerControl:QueryList<DisableControlDirective>
  //see that make public ngControl to use when check if a innerControl has 
  //the directive
  constructor(@Optional() public ngControl: NgControl) {}
}

If we apply the directive to a fromGroup, disabled/enabled all the inners controls. For get the inners control we are using @ContentChildren(NgControl). When we inject NgControl, we need put @Optional() because in a FormGroup we has NO NgControl -remember that NgControl is anything that has [formControlName], [NgModel] or [formControl] directive

We need check if our inner controls has no [disableControl] directive. In this case we need leap this control. For this, we need get these controls using @ContentChildren(DisableControlDirective) and check it.

Our form becomes very "easy", but we pass as @Input the edit mode to the child

<form [formGroup]="parentForm" [disableControl]="!editMode" (ngSubmit)="onSave()">
  <input formControlName="id">
  <input formControlName="name">
  <app-child-component [editMode]="editMode"></app-child-component>
</form>

//and our children component

<form [formGroup]="identifyForm" [disableControl]="!editMode">
  <input type="radio" name="identity" value="yes" formControlName="identity"> Yes
  <input type="radio" name="identity" value="no" formControlName="identity" > No

  <input formControlName="identityname" 
     [disableControl]="!editMode || identifyForm.get('identity').value=='no'">
</form>

We can see the final result in this stackblitz

Eliseo
  • 50,109
  • 4
  • 29
  • 67