1

TL;DR: Combining nested components with nested reactive forms, appears to be problematic. Each nested component must build the entire form hierarchy from a combination of [formGroupName], [formArrayName] and [formControlName] directives.

Detail: Given a key-value pair is modeled as:

{
    "key": string,
    "value": string
}

A single key-value pair and a list of key-value pairs could be modeled as:

{
  "one": {
    "key": "Key A",
    "value": "Value A"
  },
  "many": [
    {
      "key": "Key A",
      "value": "Value A"
    },
    {
      "key": "Key B",
      "value": "Value B"
    },
    {
      "key": "Key C",
      "value": "Value C"
    }
  ]
}

It looks like a combination of Angular Reactive Forms and nested @Component should make this trivial.

I created the following component hierarchy and project structure:

│   app.component.html
│   app.component.ts
│   app.module.ts
│
├───key-value
│       key-value.component.html
│       key-value.component.ts
│
└───key-value-list
        key-value-list.component.html
        key-value-list.component.ts

The application component, app.component.ts, defines a model and a corresponding FormGroup built using FormBuilder:

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html'
})
export class AppComponent {
  public form: FormGroup;
  public model = {
    one: {
      key: 'Key A',
      value: 'Value A'
    },
    many: [
      {
        key: 'Key A',
        value: 'Value A'
      },
      {
        key: 'Key B',
        value: 'Value B'
      },
      {
        key: 'Key C',
        value: 'Value C'
      }
    ]
  };

  constructor(private readonly fb: FormBuilder) { }

  public ngOnInit(): void {
    const items: FormGroup[] = this.model.many.map(pair => {
      return this.fb.group(pair);
    });

    this.form = this.fb.group({
      one: this.fb.group(this.model.one),
      many: this.fb.array(items)
    });
  }
}

The model introduced at the beginning should be easily identifiable. form contains two controls, FormGroup and FormArray for key-value and key-value-list respectively. FormArray is a list of FormGroups.

app.component.html provides the top-level form and the name of the control group the key-value pair should belong to:

<key-value [parentForm]="form" name="one"></key-value>
<pre>{{ form.value | json }}</pre>

Form values are piped out for debugging and formatted (<pre>). The key-value.component.ts accepts two Input() values and does very, very little (all the work is done in the view):

@Component({
  selector: 'key-value',
  templateUrl: './key-value.component.html'
})
export class KeyValueComponent {
  @Input() public parentForm: FormGroup;
  @Input() public name: string;
}

The work is done in key-value.component.html using the two Input() values to construct a hierarchy of controls:

<div [formGroup]="parentForm">
    <div [formGroupName]="name">
        <mat-form-field>
            <input matInput formControlName="key" placeholder="Key">
        </mat-form-field>
        <mat-form-field>
            <input matInput formControlName="value" placeholder="Value">
        </mat-form-field>
    </div>
</div>

The list view is similar:

<div [formGroup]="parentForm">
    <div [formArrayName]="name">
        <div *ngFor="let c of parentForm.get(name).controls; let i=index;" [formGroupName]="i">
            <mat-form-field>
                <input matInput formControlName="key" placeholder="Key">
            </mat-form-field>
            <mat-form-field>
                <input matInput formControlName="value" placeholder="Value">
            </mat-form-field>
        </div>
    </div>
</div>

This works. But I'd like to re-use key-value component in the key-value-list component, but I'm hitting a wall. Assuming my application view becomes:

<div [formGroup]="parentForm">
    <div [formArrayName]="name">
        <div *ngFor="let c of parentForm.get(name).controls; let i=index;" [formGroupName]="i">
            <key-value [parentForm]="??????" name="??????"></key-value>
        </div>
    </div>
</div>

This seems a reasonable start, but I don't know what the inside of the loop should be.

6
  • Why don't you make the sub-component a control value accessor? angular.io/api/forms/ControlValueAccessor Commented Feb 3, 2018 at 20:12
  • On key-value? And. Should that be necessary? It may be possible, but it feels a little involved for merely rendering a list of editable components? Commented Feb 3, 2018 at 20:27
  • Why any? Isn't each one representing a single key-value? Perhaps you could edit to clarify, there's a lot going on in your question. Commented Feb 3, 2018 at 20:30
  • Sorry, I made a bad typo. any-value should have been key-value (fixed previous comment too). Commented Feb 3, 2018 at 20:33
  • I don't have a solution for you but I do want to raise couple of questions: 1. The way you construct your one is to make it as a FormGroup and hello-component is used to render a FormGroup with 2 controls: key and value'. However, your many` is a FormArray of FormControls, not FormArray of FormGroups. Hence, I do not think it is possible to "re-use" hello-component in hello-list 2. As you "re-use" hello, you also "re-render" the parentForm in your hello template. Commented Feb 4, 2018 at 0:29

1 Answer 1

3

Actually, you don't need to pass both, FormGroup instance and FormGroupName to the child component, either one will do. Arrays are a bit trickier than single control but something like this should work:

<div [formGroup]="parentForm">
    <div [formArrayName]="name">
        <div *ngFor="let c of parentForm.get(name).controls; let i=index;">
            <key-value [parentForm]="c"></key-value>
            <!--<key-value [name]="i"></key-value> this should also work-->
        </div>
    </div>
</div>
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.