2

Alright, let me start with describing the problem I am actually trying to solve.

I need my form model to have the this structure:

{
  nested: {
    first: 'one',
    second: 'two',
    third: 'three',
    forth: 'four', ...
  }, ...
}

The child properties will be added to the model dynamically based on the users input, and the number of nested levels can vary.

The implementation looks something like this:

Initialize form:

ngOnInit() {
  this.form = this.fb.group({
    nested: this.getControls() // of type FormGroup
  });
}

Load controls:

getControls(): FormGroup {
  let group: FormGroup = new FormGroup({});
  Object.keys(this.controls).forEach((key) => {
    let control: FormControl = new FormControl(this.controls[key]);
    group.addControl(key, control);
  })
  return group
}

Template with *ngFor:

<div *ngFor="let control of form.get('nested').controls | keys; let i = index">
  <label>{{ getLabel(i) }}</label>
  <input [attr.placeholder]="control.value">
</div>

With an add new control method:

addControl(key, value) {
  let control: FormControl = new FormControl(value)
  this.form.get('nested').addControl(key, control);
  this.cdf.detectChanges();
}

A stripped down implementation available on Plunkr

Now, the problem I have is that the values updates for the model but the template is not re-rendering with the added inputs.

I have tried to force the change detection to fire but it has not worked.

I understand that using FormArray provides more dynamic features, but it produces an output as this:

{
  nested: [
    { first: 'one' },
    { second: 'two' },
    { third: 'three' },
    { forth: 'four' }, ...
  ], ...
}

Which brings us back to source of the problem.

Of course, I could utilize a function which transform back the model to the desired format before I push it on to the database, but given that the model could have several different properties at different levels in the tree, whereas for some I might be displayed as arrays and some might not, things can easily get overly complex.

Which leaves me with FormGroup, but then how do I make sure the template renders as new input is applied? Thanks!

unitario
  • 6,295
  • 4
  • 30
  • 43

1 Answers1

2

I can offer you the following solutions:

1) Using impure pipe

@Pipe({
  name: 'keys',
  pure: false
})

Plunker Example

2) Passing new reference after adding control

addControl(key, value) {
  this.controls[key] = value;
  this.form = this.fb.group({
    nested: this.getControls()
  });
}

Plunker Example


I also removed getLabel function and added mapping control name to your pipe

keyArr.forEach((key) => {
   value[key].name = key;
   dataArr.push(value[key])
})

See also

Community
  • 1
  • 1
yurzui
  • 205,937
  • 32
  • 433
  • 399
  • Alright! I never suspected the pipe, will definitely investigate further on pure vs. impure pipes. Perfect answer. – unitario Feb 07 '17 at 20:22