Reputation: 2234
I have a component that displays some form fields depending of a given object. Normally, the component is called in a loop by a parent component that gives a new object each time, then, the child component shows the right element depending on the type.
Here is the StackBlitz example
But sometimes, the element could be an iterable, a repeatable element (in case of a FormArray
). I have the following code:
<div [formGroup]="form">
<ng-template #formTmpl
let-field="field"
let-index="index">
<pre>index: {{ index }}</pre>
<label [attr.for]="!!question.iterable ? question.key + index : question.key">{{ question.label }}</label>
<div [ngSwitch]="question.controlType">
<div [attr.formArrayName]="!!question.iterable ? question.key : null">
<input *ngSwitchCase="'textbox'"
[class]="isValid ? config.validClass : config.invalidClass"
[formControlName]="!!question.iterable ? index : question.key"
[placeholder]="question.placeholder"
[id]="!!question.iterable ? question.key + index : question.key"
[type]="question['type']">
<select [id]="question.key"
*ngSwitchCase="'dropdown'"
[class]="isValid ? config.validClass : config.invalidClass"
[formControlName]="question.key">
<option value=""
disabled
*ngIf="!!question.placeholder"
selected>{{ question.placeholder }}</option>
<option *ngFor="let opt of question['options']"
[value]="opt.key">{{ opt.value }}</option>
</select>
<textarea *ngSwitchCase="'textarea'"
[formControlName]="question.key"
[id]="question.key"
[class]="isValid ? config.validClass : config.invalidClass"
[cols]="question['cols']"
[rows]="question['rows']"
[maxlength]="question['maxlength']"
[minlength]="question['minlength']"
[placeholder]="question.placeholder"></textarea>
</div>
</div>
<div class="errorMessage"
*ngIf="!isValid">{{ question.label }} is required</div>
</ng-template>
<div *ngIf="question.iterable; else formTmpl">
val {{questionArray.value | json}}
<div *ngFor="let field of questionArray.controls; let i=index; last as isLast">
<ng-container [ngTemplateOutlet]="formTmpl"
[ngTemplateOutletContext]="{field: field, index: i}"></ng-container>
<button *ngIf="question.iterable && isLast"
type="button"
(click)="addItem(question)">+</button>
</div>
</div>
</div>
for the moment, I'm testing the repeatability of an element on inputs
only.
So for one element, it works well, but it can't get the reference to the optional directive: [attr.formArrayName]="!!question.iterable ? question.key : null"
If try the following (putting the input directly in the ng-container
, without calling the ng-template
:
<div *ngIf="question.iterable; else formTmpl">
val {{questionArray.value | json}}
<div *ngFor="let field of questionArray.controls; let i=index; last as isLast">
<input [class]="isValid ? config.validClass : config.invalidClass"
[formControlName]="i"
[placeholder]="question.placeholder"
[id]="question.key"
[type]="question['type']">
<button *ngIf="question.iterable && isLast"
type="button"
(click)="addItem(question)">+</button>
</div>
</div>
the input field shows well, and the value of the Form is well bind to it.
What's strange is that the formControlName
of each field inside the ng-template
have their value bind well to the form.
The error I got at the form init is:
ERROR Error: Cannot find control with unspecified name attribute
at _throwError (vendor.js:74769)
at setUpControl (vendor.js:74593)
at FormGroupDirective.addControl (vendor.js:78338)
at FormControlName._setUpControl (vendor.js:78989)
at FormControlName.ngOnChanges (vendor.js:78912)
at checkAndUpdateDirectiveInline (vendor.js:59547)
at checkAndUpdateNodeInline (vendor.js:70213)
at checkAndUpdateNode (vendor.js:70152)
at debugCheckAndUpdateNode (vendor.js:71174)
at debugCheckDirectivesFn (vendor.js:71117)
Then, if I add a new element to the formArray, the new element appears but isn't bind, and this new errors shows:
ERROR Error: Cannot find control with name: '1'
at _throwError (vendor.js:74769)
at setUpControl (vendor.js:74593)
at FormGroupDirective.addControl (vendor.js:78338)
at FormControlName._setUpControl (vendor.js:78989)
at FormControlName.ngOnChanges (vendor.js:78912)
at checkAndUpdateDirectiveInline (vendor.js:59547)
at checkAndUpdateNodeInline (vendor.js:70213)
at checkAndUpdateNode (vendor.js:70152)
at debugCheckAndUpdateNode (vendor.js:71174)
at debugCheckDirectivesFn (vendor.js:71117)
So how can I make the field taking in account the formArrayName
property ?
You can see below the attribute is present in the markup (but I thought it were ng-reflect-name
normally), but it's not working anyway:
As final example, here's a screenshot of the form (notice the indexes above each repeatable inputs):
Here is the StackBlitz example
Upvotes: 1
Views: 1395
Reputation: 4854
i think [attr.formArrayName]="!!question.iterable ? question.key : null"
binding doesn't work because it is performed as attribute binding instead of input binding
however, when we convert it to input binding new errors occur for non-FormArray questions in your use case.
so, using good-old formControl
directive instead of formControlName
solves the problem.
[formControl]="form.get(question.iterable ? [question.key, index] : question.key)"
also with this approach you don't need [attr.formArrayName]="!!question.iterable ? question.key : null"
binding anymore
here is a working demo
Upvotes: 4