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.
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? – Jackany
? Isn't each one representing a single key-value? Perhaps you could edit to clarify, there's a lot going on in your question. – jonrsharpeany-value
should have beenkey-value
(fixed previous comment too). – Jackone
is to make it as aFormGroup
andhello-component
is used to render aFormGroup
with 2 controls:key
andvalue'. However, your
many` is aFormArray
ofFormControls
, notFormArray
ofFormGroups
. Hence, I do not think it is possible to "re-use"hello-component
inhello-list
2. As you "re-use"hello
, you also "re-render" theparentForm
in yourhello
template. – Chau Tran