ng-dynamic-forms icon indicating copy to clipboard operation
ng-dynamic-forms copied to clipboard

How to populate form with default values on form creation?

Open monstrfolk opened this issue 6 years ago • 9 comments

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 avatar Feb 09 '19 07:02 monstrfolk

@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 avatar Feb 09 '19 19:02 rernens

@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 avatar Feb 10 '19 04:02 monstrfolk

@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 avatar Feb 10 '19 16:02 rernens

@rernens, can you give an example using embedded form groups please?

monstrfolk avatar Feb 12 '19 03:02 monstrfolk

@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 );
  }

rernens avatar Feb 13 '19 09:02 rernens

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`);
        }
      }
    }
  });
}

jfcere avatar Aug 02 '19 17:08 jfcere

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.

davnat avatar Oct 20 '19 20:10 davnat

Reopening this issue. Would be nice to get a consensus on how to populate forms with preexisting data.

monstrfolk avatar Nov 24 '19 22:11 monstrfolk

Any update on this? Now I also see I can't update values in ngOnInit: form is just empty in this case.

asvishnyakov avatar Aug 17 '21 10:08 asvishnyakov