Reputation: 174
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
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
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:
{}
instead of null;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.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
Reputation: 1106
Two solutions come to mind
@NgModule({...})
export class AppModule {
constructor() {
const builtInFunction = FormGroup.prototype.patchValue;
FormGroup.prototype.patchValue = function (value, options?) {
builtInFunction.call(this, value || {}, options);
}
}
}
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
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
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
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
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