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

valueUpdates.next(value) not updating the value of FormArrayGroup controls

Open rernens opened this issue 7 years ago • 13 comments

I'm submitting a


[x] Bug / Regression
[ ] Feature Request / Proposal
[ ] Question

I'm using


NG Dynamic Forms Version: `5.1.0`

[ ] Basic UI
[ ] Bootstrap UI  
[ ] Foundation UI
[ ] Ionic UI
[ ] Kendo UI
[x] Material  
[ ] NG Bootstrap
[ ] Prime NG

Description

I am using a form to import data employee data from a file.

The form is used to map the content of the file to the database fields.

I am using the following formModel

export const PEOPLE_IMPORT_FORM_MODEL = [
  
  new DynamicFormGroupModel({
    id: 'settings-fg',
    group: [
      new DynamicSelectModel({
        id: 'source',
        placeholder: 'aucun fichier sélectionné',
        hint: 'cliquer sur le dossier pour choisir un ou plusieurs fichiers',
        multiple: true,
        options: [],
        validators: {
          required: null
        },
        errorMessages: {
          required: 'Un fichier au moins est requis !'
        }
      }),
      new DynamicSelectModel<string>({
        id: 'separator',
        placeholder: 'Type de séparateurs',
        hint: 'choisir dans la liste',
        multiple: false,
        value: '\t',
        options: [
          {
            label: 'Virgule',
            value: ','
          },
          {
            label: 'Point-virgule',
            value: ';'
          },
          {
            label: 'Tabulation',
            value: '\t'
          }
        ],
        validators: {
          required: null
        },
        errorMessages: {
          required: 'Champ obligatoire'
        }
      }),

      new DynamicCheckboxModel({
        id: 'labels',
        label: 'Ignorer le premier enregistrement'
      })
    ]
  }),

  new DynamicFormArrayModel({
    id: 'fields',
    initialCount: 0,
    groupFactory: () => {
      return [
        new DynamicSelectModel<string>({
          id: 'field',
          disabled: false,
          placeholder: 'champ associé à ',
          multiple: false,
          options: fieldOptions,
          validators: {
            required: null
          },
          errorMessages: {
            required: 'Obligatoire'
          }
        })
      ];
    }
  })
];

The FormArray is dynamically built based on data extracted from a file, precisely the first row of the first file that serves for fields mapping.

The FormArray ends up with one row per column in the fields mapping record.

Place holder and initial values are automatically set using a mix of config file and column names.

capture d ecran 2017-11-28 a 17 23 02

while everything works fine for placeholder but fails for the value when using valueUpdates.next(value) but works with the following code

private populateColumns( record ) {

    this.log.debug('Organization Details Entity People Component - populateColumns');

    this.columns = record.split( this.separatorModel.value );

    const fieldsControl = this.formGroup.get('fields') as FormArray;

    this.formService.clearFormArray(fieldsControl, this.fieldsModel);

    for (let i = 0; i < this.columns.length; i++) {

      this.formService.addFormArrayGroup(fieldsControl, this.fieldsModel);

      const model = this.fieldsModel.groups[i].group[0] as DynamicSelectModel<string>;

      model.placeholder = 'Champ associé à la colonne « ' + this.columns[i] + ' »';

      const indx = _.findIndex(this.fields, (o) =>
        _.findIndex( o.alias, (e) => e.toLowerCase() === this.columns[i].toLowerCase() ) !== -1
        || o.field.toLowerCase() === this.columns[i].toLowerCase()
      ) ;

      if ( indx !== -1 ) {
        const value = this.fields[indx].field;

        // model.valueUpdates.next( value );   
        // ==> updates the model value but not the formcontrol value

        const row = fieldsControl.controls[i];
        const controls = row['controls'];
        const field = controls['field'];
        field.setValue(value);
      }
    }

    fieldsControl.updateValueAndValidity();

  }

valueUpdates.next(value) update the model value but not the corresponding form control value

rernens avatar Nov 28 '17 16:11 rernens

I can confirm the same issue...when doing model.valueUpdates.next( value ) the model gets updated but form control does not. If the form is reloaded without clearing, the form will show the values i.e. pushing the .next value doesn't trigger the control in an array to re-render as needed. If there is a way to trigger a render that might solve it.

Alternately I'm wondering if there's a way to push the value into an object which is used to created the inserted control, to avoid the rendering issue.

Will update if I find an answer and appreciate if you can do the same.

sarora2073 avatar Dec 03 '17 17:12 sarora2073

@rernens @sarora2073 Hey there!

I'm sorry, but I can't verify a bug here at the moment.

Please have a look at the Bootstrap sample component.

There's test code for that use case and it's working flawless:

    ngOnInit() {

        this.formGroup = this.formService.createFormGroup(this.formModel);

        this.arrayControl = this.formGroup.get("bootstrapFormGroup2").get("bootstrapFormArray") as FormArray;
        this.arrayModel = this.formService.findById("bootstrapFormArray", this.formModel) as DynamicFormArrayModel;
    }

    test() {

        this.arrayModel.get(1).group[0].valueUpdates.next("This is just a test");
    }

udos86 avatar Dec 03 '17 18:12 udos86

Hi @udos86,

Thanks for the quick response. I think i see what both @rernens and I are doing different from your test code. I was creating a reference to the control in the array model and then calling .valueUpdates on that control (a DynamicInputModel type). The reason I went down this path is that typescript was objecting to doing it the way you show..but obviously this is not a valid workaround.

So anyway, now that I'm getting back on track and trying to follow the sample code, I need to figure out why I can't get it to work. Specifically, here's the code, and associated typescript error:

let phoneArrayModel = this.formService.findById('phones', this.formModel); phonesArrayModel.get(0).group[0].valueUpdates.next('1-800-111-2222');

Property 'valueUpdates' does not exist on type 'DynamicFormControlModel'

I see this kind of issue has come up more than once on this forum so apologies in advance if it's a repeat.

Regards,

  • S. Arora

sarora2073 avatar Dec 03 '17 18:12 sarora2073

Ok, read through the closed issues and got it working I think.

  1. to resolve the typescript issue need to cast the array model like so: (phonesArrayModel.get(0).group[0] as DynamicFormValueControlModel).valueUpdates.next('1-800-111-2222');

  2. Per issue #225 it's necessary to call this.formService.createFormGroup BEFORE AND AFTER valueUpdates..this appears to be "the" trick to fix the rendering issue I described previously, and it appears there was a not a problem to call .valueUpdates on the control per se, as @rernens and I were doing previously.

If in agreement on approach suggest the readme get updated accordingly. Glad to do a PR if you like.

sarora2073 avatar Dec 03 '17 20:12 sarora2073

@udos86

Hi Udo !

I am wondering if the problem is not really related to the changeDetectionStrategy.onPush that I am using which is the sole difference that I can see with you sample ( or the fact that what I am trying to get updated is a select ).

Will look deeper into it as soon as I find some time.

rernens avatar Dec 04 '17 07:12 rernens

@rernens

I am wondering if the problem is not really related to the changeDetectionStrategy.onPush

I'm pretty sure that this is causing your problems.

When using this change detection you actually need to create new instance of your form (control) model to trigger a component update

udos86 avatar Dec 07 '17 20:12 udos86

@udos86

Hi Udo

When using this change detection you actually need to create new instance of your form (control) model to trigger a component update

I am not sure to understand your statement as the following code works without recreating the control

    // this does not work
    // const model = this.fieldsModel.groups[i].group[0] as DynamicSelectModel<string>;
    // model.valueUpdates.next( value );   
    // ==> updates the model value but not the formcontrol value

    // This works
    const fieldsControl = this.formGroup.get('fields') as FormArray;
    const row = fieldsControl.controls[i];
    const controls = row['controls'];
    const field = controls['field'];
    field.setValue(value);

rernens avatar Dec 11 '17 09:12 rernens

@sarora2073

Glad to do a PR if you like.

Great! Just do it :-)

@rernens

I am not sure to understand your statement as the following code works without recreating the control

Alright, could you finally figure out what's causing this or are you still facing these problems?

udos86 avatar Dec 15 '17 23:12 udos86

@udos86

Still having the pb but have circumvented it using standard formcontrol access not through the model

rernens avatar Dec 18 '17 08:12 rernens

Hi all,

Facing similar problems and can't fix it. Here what I do:

Form model factory :

export function getFormModel(): DynamicFormControlModel[] {
  return [
    new DynamicSelectModel<ExamSpecification>({
      id: 'examSpecification',
      multiple: false,
      filterable: true,
      options: [],
    })
  ];
}

Add the options to the select model and select the right option.

this.formModel = getFormModel();
this.formGroup = this.formService.createFormGroup(this.formModel);

this.examSpecificationService.getAll().subscribe(examSpecifications => {
  const examSpecificationSelect = this.formService.findById('examSpecification', this.formModel) as DynamicSelectModel<ExamSpecification>;
  for (const examSpecification of examSpecifications) {
    examSpecificationSelect.add({
      label: examSpecification.name,
      value: examSpecification,
    });
  }
  
  this.formGroup.get('examSpecification').setValue(this.dataObject['examSpecification']);
  console.log(this.formGroup.get('examSpecification').value); // displays the right value in console, but not in select/dropdown
});

This keeps selecting the first option. I also did something similar with a multiple select, but it work quite fine! Did someone face the same problem ?

damianorenfer avatar Dec 18 '17 15:12 damianorenfer

@sarora2073

Per issue #225 it's necessary to call this.formService.createFormGroup BEFORE AND AFTER valueUpdates..this appears to be "the" trick to fix the rendering issue I described previously, and it appears there was a not a problem to call .valueUpdates on the control per se, as @rernens and I were doing previously.

This did the trick for me! 🎉

andrewjrhill avatar Jan 10 '18 08:01 andrewjrhill

@rernens If you import form data from a file, then by calling model.valueUpdates.next() you likely trigger value changes as part of a side-effect (e.g. an XHRRequest or something similar) which is not a DOM event. Then Angular's change detection mechanism might not recognise the value changes thus not running the change detection automatically. You may need to trigger it manually.

To test my hypothesis you could try to subscribe to model.valueUpdates and trigger change-detection manually in the subscriber, e.g. by doing something like

model.valueUpdates.subscribe(() => {
    // cd is an instance of Angular's ChangeDetectorRef. Get it via dependency injection.
    this.cd.detectChanges();  
});

If it works @udos86 might be able to evaluate whether ng-dynamic-forms can handle this by default (without performance degredation) or whether it is a use case that might be specific to your app.

about-code avatar Mar 24 '18 15:03 about-code

Can someone check if this is still an issue?

I think it was related to Material-UI using the DetectionStrategy.OnPush

Karamuto avatar Aug 06 '18 19:08 Karamuto