Thom
Thom

Reputation: 174

How to let Angular Reactive Forms PatchValue() ignore null value or undefined when patching a FormGroup

For an application I'm building, I'm retrieving data from an API and setting my form value directly with that response. For that I'm using the reactive forms module. I've built the form so that it matches the object I'm getting from the API. The problem is that some fields are supposed to be an object (FormGroup), but are NULL when empty. This results in the error "Cannot convert undefined or null to object".

FormGroup instance:

note: this.formBuilder.group({
   turnbook_file: [''],
   note: [''],
   additional_files: ['']
});

Response from API

note: NULL

My thought was that maybe it's possibile to put an OR statement or something where you define the FormGroup like this:

note: this.formBuilder.group({
   turnbook_file: [''],
   note: [''],
   additional_files: ['']
} | NULL);

Upvotes: 14

Views: 12417

Answers (7)

Flash Noob
Flash Noob

Reputation: 500

improving bestbrain10 answer. You can use pickBy and isDefined together.

import { isEmpty, pickBy ,isUndefined,isNull} from 'lodash';
// Fetch data and remove all falsy  keys

this.formgroup.patchValue(
_.omitBy(formData, (v) => _.isUndefined(v) || _.isNull(v) || v === ''));

Upvotes: 1

shokha
shokha

Reputation: 3179

Short answer: In the current implementation of Angular Forms API - it's not possible to create/patch FormGroup with null or undefined value.

Reason: In all scenarios new FormGroup(), formBuilder.group(), patchValue() etc there is a call of a method Object.keys(), which throws an error Cannot convert undefined or null to object (according to JS specification) if we pass null/undefined as argument.

Proof:

  • FormBuilder's group() method (source code link):

      group(controlsConfig: {[key: string]: any}, ...) {
        const controls = this._reduceControls(controlsConfig);
        ...
      }
      _reduceControls(controlsConfig: {[k: string]: any}) {
        Object.keys(controlsConfig).forEach(...);
        ...
      }
    
  • FormGroup's patchValue (source code link):

    patchValue(value: {[key: string]: any}, ...) {
      Object.keys(value).forEach(...);
      ...
    }
    

Workarounds:

  • Change you API response in Backend side so it will send empty object {} instead of null;
  • Once you receive a response in Frontend side, you can do mapping: from null to {};
  • Extend FormGroup class and create a new one where you can override the constructor/patchValue: check if the argument is null/undefined - use empty object instead.
  • Extend FormBuilder service and create your own Singleton service (use it everywhere in your app) where you can override the group method (see prev point).

Upvotes: 1

displayName
displayName

Reputation: 1106

Two solutions come to mind

Modify FormGroup prototype

@NgModule({...})
export class AppModule {

  constructor() {
    const builtInFunction = FormGroup.prototype.patchValue;
    FormGroup.prototype.patchValue = function (value, options?) {
      builtInFunction.call(this, value || {}, options);
    }
  }

}

Extend FormGroup class

import {FormGroup, ValidatorFn, AbstractControlOptions, AsyncValidatorFn} from '@angular/forms';

export class MyFormGroup extends FormGroup {

  constructor(
    controls: {[key: string]: any},
    validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null,
    asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null
  ) {
    super(controls, validatorOrOpts, asyncValidator);  
  }

  patchValue(value: {[key: string]: any}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}) {
    super.patchValue(value || {}, options);
  }

}

Upvotes: 1

Adithya Sreyaj
Adithya Sreyaj

Reputation: 1892

You can simply filter out those values before patching the form

const obj = {
  foo: "bar",
  bar: null,
  cat: undefined,
  dog: "bark"
}

const truthy = Object.keys(obj).filter(item => obj[item] != undefined || obj[item] != null );
const newObj = {};
truthy.forEach(item =>Object.assign(newObj, { [item]: obj[item]}));
console.log(newObj) // { foo: "bar", dog: "bark" }

Once you filter out the falsy values, just use it to patch the form

Upvotes: 0

bestbrain10
bestbrain10

Reputation: 161

You can try using lodash pickBy method to wrap your api response before using it.

import * as _ from 'lodash';
...
//fetch data from API
note.patchValue(
    _.pickBy(apiResponse)
);
...

Upvotes: 3

Prachi Shah
Prachi Shah

Reputation: 88

If you define form and controls then you have to must pass values to that so if your response is null then set values of Form controls like this

note.setValue({
'turnbook_file': data.turnbook_file == null ? "": data.turnbook_file,
   'note: ,data.note == null ? "": data.note,
   'additional_files': data.additional_files == null ? "": 
    data.additional_files,
})

Upvotes: 0

Prachi Shah
Prachi Shah

Reputation: 88

You may define your form like this

note =new FormGroup({
   'turnbook_file': new FormControl(null),
   'note: new FormControl(null),
   'additional_files': new FormControl(null)
})

Upvotes: 1

Related Questions