ng-dynamic-forms
ng-dynamic-forms copied to clipboard
How to populate form with default values on form creation?
I'm submitting a
[ ] Bug / Regression
[ ] Feature Request / Proposal
[x] Question
I'm using
NG Dynamic Forms Version: `7.0.4`
[ ] Basic UI
[ ] Bootstrap UI
[ ] Foundation UI
[ ] Ionic UI
[ ] Kendo UI
[ ] Material
[x] NG Bootstrap
[ ] Prime NG
Description
How to populate form with default values on form creation? Is there a method to do this included with Dynamic Forms?
Example form model:
[ new DynamicInputModel({ id: 'email', inputType: 'email' }), new DynamicInputModel({ id: 'name', inputType: 'text' }), new DynamicInputModel({ id: 'surname', inputType: 'text' }) ]
Data loaded from DB in JSON form:
{ "email": "[email protected]", "name": "First", "surname": "Last" }
@monstrfolk
use the following method
public dataToForm( data: any, formModel: DynamicFormModel ) {
Object.keys(data).forEach( key => {
const model = this.formService.findById( key, formModel ) as DynamicInputModel;
if ( model ) {
model.valueUpdates.next( data[key] );
}
});
}
this method will have to be extended in case you form is more complex ie contains arrays.
@rernens, Thanks.
Best way I found to update form values is with patchValue. Does not work for checkbox components.
https://github.com/udos86/ng-dynamic-forms/issues/929
@monstrfolk
patchValue is a formGroup method not a formModel method, I strongly recommend to use the formModel methods when using ng-dynamic-forms.
I do that in hundreds of forms with data coming from dbs and it works for all types of fields like a charm.
@rernens, can you give an example using embedded form groups please?
@monstrfolk
assume your data structure is the following and matches the way your form is built:
{
name: 'my name',
birthDate: 'any date',
address: {
name: 'my address name',
streets: [ 'my street line1', 'my street line2'],
zip: 'my zip code',
city: 'my city',
country: 'my country'
},
courses: [
{ code: 'code1', name: 'name of course', price: 100 },
....
]
}
assuming that the structure of your json data matches the structure of your form ( json keys match form ids) this could do the trick ( Caution ! untested code, this is an excerpt of one of my data to form methods ).
...
public formGroup: FormGroup;
public formModel: DynamicFormModel = MY_MODEL;
public formLayout: DynamicFormLayout = MY_LAYOUT;
...
constructor( private formService: DynamicFormService,
private dataService: DataService ) {
}
ngOnInit() {
this.formGroup = this.formService.createFormGroup(this.formModel);
this.data.get( 'myData').subscribe( data => {
this.formGroup.reset(); // ensure form is totally empty
this.dataToForm( data, this.formModel );
})
}
private dataToForm( data: any, formModel: any ) {
let fieldModel;
Object.keys( data ).forEach( key => {
if ( typeof data[key] !== 'object' ) {
const groupModel = this.formService.findById( key, formModel);
if ( groupModel instanceof DynamicFormGroupModel ) {
if ( data[key] instanceof Array ) {
throw new Error(`Error : $(key) data structure should be an object`) ;
} else {
this.dataToForm( data[key], formModel.group );
/* could also be simply
this.dataToForm( data[key], formModel );
as formService.findById searches recursively within embedded FormGroupModels
but won't work if multiple fields have the same id as in the sample above : ie name
*/
}
} else if ( groupModel instanceof DynamicFormArrayModel ) {
if ( data[key] instanceof Array ) {
for ( let i = 0; i < data[key].length; i++ ) {
this.dataToForm( data[key][i], groupModel.groups[i].group );
}
} else {
throw new Error(`Error : $(key) data structure should be an array`) ;
}
}
} else {
fieldModel = this.formService.findById( key, formModel );
if ( fieldModel ) {
this.setFormValue( fieldModel, data[key]);
} else if ( formModel instanceof DynamicFormValueControlModel ) {
this.setFormValue( formModel, data[key]);
} else {
throw new Error(`Error : $(key) data structure missing in form`) ;
}
});
}
private setFormValue ( model: DynamicFormValueControlModel<any>, value: any ): void {
model.valueUpdates.next( value );
}
Proposition
Assigning values directly to FormGroup using FormGroup.setValue(data) or FormGroup.reset(data) update the values correctly but unfortunately doesn't trigger the reevaluation of the MATCHERS.
ngOnInit() {
this.formGroup = this.formService.createFormGroup(this.formModel);
this.formGroup.setValue(data);
}
❓ If there could be a way to trigger the reevaluation of the matchers programmatically this could be a nice workaround ... or even better modify the code so that when a value is updated on the FormControl the related field from the model automatically reevaluate its matchers.
Workaround
In the meanwhile I adjusted @rernens solution as it wasn't working out-of-the-box...
ngOnInit() {
this.formGroup = this.formService.createFormGroup(this.formModel);
this.dataToForm(data, this.formModel);
}
private dataToForm(data: any, model: any) {
let fieldModel: any;
Object.keys(data).forEach(key => {
if (typeof data[key] !== 'object') {
const groupModel = this.formService.findById(key, model);
if (groupModel instanceof DynamicFormGroupModel) {
if (data[key] instanceof Array) {
throw new Error(`Error : $(key) data structure should be an object`);
} else {
this.dataToForm(data[key], model.group);
// could also be simply
// this.dataToForm(data[key], model);
// as formService.findById searches recursively within embedded FormGroupModels
// but won't work if multiple fields have the same id as in the sample above : ie name
}
} else if (groupModel instanceof DynamicFormArrayModel) {
if (data[key] instanceof Array) {
for (let i = 0; i < data[key].length; i++) {
this.dataToForm(data[key][i], groupModel.groups[i].group);
}
} else {
throw new Error(`Error : $(key) data structure should be an array`);
}
} else {
fieldModel = this.formService.findById(key, model);
if (fieldModel) {
fieldModel.valueUpdates.next(data[key]);
} else if (model instanceof DynamicFormValueControlModel) {
model.valueUpdates.next(data[key]);
} else {
throw new Error(`Error : $(key) data structure missing in form`);
}
}
}
});
}
As mentioned in changelog and in #1025, starting from version 9.0.0 valueUpdates has been removed.
This is my WIP solution using the new interface:
setFormGroupValue(
formGroup: FormGroup,
formModel: DynamicFormControlModel[],
data: any
) {
formModel.forEach(ctrl =>
this.setFormValue(
formGroup.controls[ctrl.id] as FormArray,
ctrl,
data[ctrl.id]
)
);
}
private setFormValue(
formArray: FormArray,
ctrl: DynamicFormControlModel,
data: any
) {
switch (ctrl.type) {
case 'ARRAY':
const control = ctrl as DynamicFormArrayModel;
const formGroups = control.groups;
while (data.length > formGroups.length) {
this.formService.addFormArrayGroup(formArray, control);
}
formGroups.forEach(
(fieldsGroup: DynamicFormArrayGroupModel, idx: number) => {
if (data[idx] !== undefined) {
fieldsGroup.group.forEach(field =>
this.setFormValue(formArray, field, data[idx][field.id])
);
}
}
);
break;
case 'GROUP':
(ctrl as DynamicFormGroupModel).group.forEach(c =>
this.setFormValue(formArray, c, data[c.id])
);
break;
case 'INPUT':
case 'SELECT':
// all these should also work, but are not tested yet
// case 'COLORPICKER':
// case 'TEXTAREA':
// case 'DATEPICKER':
// case 'RADIO_GROUP':
// case 'SLIDER':
// case 'FILE_UPLOAD':
// case 'SWITCH':
// case 'RATING':
// case 'TIMEPICKER':
// case 'EDITOR':
// case 'CHECKBOX_GROUP':
// case 'CHECKBOX':
(ctrl as DynamicFormValueControlModel<any>).value = data;
break;
default:
console.warn(ctrl);
throw new Error(
`Dynamic control type '${ctrl.type}' is not supported (yet)`
);
}
}
Unfortunately, this solution doesn't fully work in case of array fields.
I have a model with a field that holds an array of objects, like this:
class MultipleField {
prop1?: string;
prop2?: string;
}
class Datum {
arrayField: MultipleField[];
}
When editing such an object I have to use a DynamicFormArrayModel, adjust the number of FormArray groups to match arrayField.length and fill all of them with the data.
The problem is, I can only fill a number of FormArray less or equal to DynamicFormArrayModel.initialCount, while the others remain all empty.
Maybe I'm missing something, or else there's a bug in the library: I still don't know.
Reopening this issue. Would be nice to get a consensus on how to populate forms with preexisting data.
Any update on this? Now I also see I can't update values in ngOnInit: form is just empty in this case.