[Fixed] Disabled controls from FormGroup (part of form custom form control) are excluded by .getRawValue() in parent

Issue

Having a component that implements ControlValueAccessor, with internal FormGroup to maintain the state of custom form control. When any field, that’s part of that FormGroup is disabled, the field isn’t visible when calling .getRawValue() method in parent form .

By specification, .getRawValue() should return the raw object, including disabled fields.

I’ve checked the code of .getRawValue() and here’s what I found:

 getRawValue(): any {
    return this._reduceChildren(
        {}, (acc: {[k: string]: AbstractControl}, control: AbstractControl, name: string) => {
          acc[name] = control instanceof FormControl ? control.value : (<any>control).getRawValue();
          return acc;
        });
  }

So, basically, when form control is instance of FormControl (that’s the case when using custom form controls, correct?), it retrieves .value instead of .getRawValue() and that’s why the disabled controls of nested form are not included in the final object.

Stackblitz demo

Steps to reproduce:

1) Click on “Disable year” button on any of three custom form controls displayed in the UI.

2) Examine the output below => .getRawValue() and .value responses are identical.

Do you have any idea how I can overcome this? I’m looking for a way to retrieve the disabled controls as well in the parent form.

Solution

Kav, in your custom form control you has

  registerOnChange(fn: (v: any) => void) {
        this.formGroup.valueChanges.subscribe(fn);
    }

so, your component return the “value” of formGroup. As a control is disabled, the value not return this field. You can change your customControl to return the rawValue of the formGroup, for this you need create a onChangeFunction, and in a ngOnInit subscribe to changes and send the rawValues. As we subscribe it’s good unsubscribe using a takeWhile and a variable

export class DetailsFields implements ControlValueAccessor,OnInit,OnDestroy {
    ...
    onChange: (v:any) => void = () => {}; //<--define a function
    isAlive:boolean=true; //<--use to unsubscribe, see below

    registerOnChange(fn: (v: any) => void) {
      this.onChange = fn; //<--equal to function
    }

    //In ngOnInit
    ngOnInit()
    {
      this.formGroup.valueChanges.pipe(takeWhile(()=>this.isAlive))
        .subscribe(v=>{
        //return this.formGroup.getRawValue()
        this.onChange(this.formGroup.getRawValue())
      })
    }
    //In ngOnDestroy
    ngOnDestroy() {  //make isAlive=False to unsubscribe
      this.isAlive=false;
    }

But in this case, you received the year always is enabled or not

There’re another aproach, that is not have a custom form control, just a component to manage the make,year and color. For this, the first is change your app-component and create the form like another form with a formArray.

<div id="cars" [formGroup]="form">
  <div formArrayName="cars">
  <div *ngFor="let car of form.get('cars').controls; let i = index;" 
   [formGroupName]="i">
    <app-details-fields [formGroup]="form.get('cars').at(i)" ></app-details-fields>
  </div>
  </div>
</div>

See that in a formArray we iterate over form.get(‘cars’).controls and we need put a [formGroupName]=”i”. In the component simply pass as input [formGroup] form.get(‘cars’).at(i)

Of course, you need change your function “createCars” to return a formGroup. not a formControl that return an object type {make:..,color:..,year}

createCar(car: any) {  //return a formGroup,not a formControl
    return this.builder.group({
      make: car.make,
      color: car.color,
      year: car.year
    });
  }

Well, the details-fields becomes easer:

details-fields.component.ts

@Component({
  selector: 'app-details-fields',
  templateUrl: './details-fields.component.html',
  styleUrls: ['./details-fields.component.css']  ,
})

export class DetailsFields {
   @Input() formGroup:FormGroup

   disableYear() {
      this.formGroup.get('year').disable();
    }

    enableYear() {
      this.formGroup.get('year').enable();
    }
}

details-fields.component.html

<div [formGroup]="formGroup">
  <div class="car-wrap">
      <div>
          <p class="title">This car is a {{formGroup.get('make').value}}</p>
      <div>
        <input type="text" formControlName="make">
        <input type="number" formControlName="year">
        <input type="text" formControlName="color">
      </div>
      <div>
        <button style="margin-top: 3px;" (click)="enableYear()">Enable year</button>
        <button style="margin-top: 3px;" (click)="disableYear()">Disable year</button>
      </div>
    </div>
  </div>
</div>

Leave a Reply

(*) Required, Your email will not be published