Creating reactive form with array of objects value in Angular - html

I need to create form in Angular.
SampleJson={
[{type:'text',name:'firstname', value:'Test'},
{type:'text',name:'lastname', value:'Test'},
{type:'mail',name:'email', value:'Test'}],[{type:'text',name:'firstname', value:'Test'},
{type:'text',name:'lastname', value:'Test'},
{type:'mail',name:'email', value:'Test'}]}
ngOninit(){
this.form=this.fb.group({
emp:this.fb.array({})
});
this.buildForm();
}
buildForm(){
const controls = sampleJson.map(option => new FormControl(option.value));
const formArray = new FormArray(controls);
}
HTML:
<form [formGroup]="form">
<div *ngFor="let field of sampleJson">
<div *ngFor="let control of field">
<ng-container [ngSwitch]="control.type">
<ng-container *ngSwitchCase="'text'">
<input type="text" [formControlName]="control.name">
</ng-container>
</ng-container>
</div>
</div`
Kindly let me know how any have idea. I need to use reactive form method. I have tried lot of different approaches.

SampleJson.forEach(item=> {
this.formGroup.addControl(
item.name, // control name
new FormControl("",)
);
});
Use this approach of adding Control from Array Object.

Related

Getting error after upgrading from Angular 8 to 12 - "Property 'controls' does not exist on type 'AbstractControl' "

I had a fully functioning code in Angular 8. I decided to upgrade from 8 to Angular 12.
I have a dynamic reactive form. The form is of question answer format.
Based on who is login in the questions change. The answer is of Yes/No format. Based on who is login in and what answer option is chosen textbox dropdown and checkbox or all the there mentioned appreas.
Here is my code for the form
Component.ts
constructor(
private router: Router,
private datePipe: DatePipe,
private route: ActivatedRoute,
private formBuilder: FormBuilder) {
this.dynamicForm = this.formBuilder.group({
questions: new FormArray([])
});
this.CreateForm();
}
get f() { return this.dynamicForm.controls; }
get t() { return this.f.questions as FormArray; }
CreateForm() {
if (this.formdetail.questionList) {
// CREATE Questions
if (racialQues !== this.formdetail.questionList[i].quetext && this.formdetail.questionList[i].quetext !== auditQues) {
this.t.push(this.formBuilder.group({
quesIndex: [i + 1],
...
controlName: [this.formdetail.questionList[i].selectedAns, [Validators.required]]
}));
} else if (this.formdetail.questionList[i].quetext === auditQues) {
this.t.push(this.formBuilder.group({
quesIndex: [i + 1],
......
selectedValue: [this.formdetail.auditTrail.selectedValue, this.reasonValidator],
}));
} else if (this.formdetail.questionList[i].quetext === racialQues) {
this.t.push(this.formBuilder.group({
quesIndex: [i + 1],
......
selectedAuxAns: [this.formdetail.questionList[i].val, [this.auxAnsValidator]]
}));
}
}
}
Here is the html
<form [formGroup]="dynamicForm">
<!-- Question Start -->
<div *ngFor="let ticket of t.controls; let i = index">
<div [formGroup]="ticket" class="form-row">
<div class="input-group col-md-12" style="padding: .15em .5em">
<div class="input-group-prepend col-md-10" style="padding: 0; margin: 0;">
<label class="input-group-text w-15">{{i + 1}}</label>
<label class="input-group-text w-100" style="text-align: left; white-space: normal;">{{ticket.controls.name.value}}</label>
</div>
<select formControlName="controlName" class="form-control col-md-2" style="height:auto !important;"
[ngClass]="{ 'is-invalid': submitted && ticket.controls.controlName.errors }"
(change)="onChange($event.target.value, i)">
<option [ngValue]="null"> --Select-- </option>
<option *ngFor="let ansItem of formdetail.questionList[i].answerList" [ngValue]="ansItem" >{{ansItem.anstext}}</option>
</select>
<div *ngIf="submitted && ticket.controls.controlName.errors" class="invalid-feedback" style="height:auto !important;"
[ngClass]="{ 'input-group-append': submitted && ticket.controls.controlName.errors,
'col-md-1': submitted && ticket.controls.controlName.errors }">
<div *ngIf="ticket.controls.controlName.errors.required">Required</div>
</div>
.
.
.
.
</div>
/div>
</div>
<!-- Question End -->
</form>
Now I am getting error in "ticket.control"
Property 'controls' does not exist on type 'AbstractControl'.
I am not sure why I am getting this error after upgrade.
I have tried ticket.['control'] or ticket.get('controlName') as suggested by other article of similar error in stackoverflow but nothing works
Here is the snapshot of error
Any help will be appreciated.
Thanks
your t is of type FormArray, FormArray.controls array is of type AbstractControl[], thus, each ticket at
<div *ngFor="let ticket of t.controls; let i = index">
line is of type AbstractControl, which does not have controls property as is seen in TS error in your screenshot. TS does not know that each ticket actually is of type FormGroup (set here this.t.push(this.formBuilder.group({...}) and which has controls property).
So you can try to add another getter tControls:
get f() { return this.dynamicForm.controls; }
get t() { return this.f.questions as FormArray; }
get tControls() { return this.t.controls as FormGroup[]; }
and use it within *ngFor:
<div *ngFor="let ticket of tControls; let i = index">
<div [formGroup]="ticket" class="form-row">
...
Please note, that now [formGroup] will also be properly filled with FormGroup and not AbstarctControl
One of your Angular upgrades probably added
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInputTypes": true,
}
to your tsconfig.json file, you can verify it just by setting these values to false and rebuilding your app, but I recommend to leave these strict checks.
This is a generic answer (so generic code) to help fixing such an issue.
Let's suppose you have a loop through a field (call it elements) of type FormArray. This field is part of a form of type FormGroup
In your ts logic code (logic implementation) you should have something like:
form = new FormGroup({
//.. other fields
elements: new FormArray([]),
});
In your template (html file), you need to toop through the elements field:
<li
*ngFor="let element of elements.controls">
{{ element.value }}
</li>
In order to tell angular cli that elements has a property of controls (so of type FormArray), you need to format it in your ts logic code as the following (implement a getter for elements field):
get elements() {
return this.form.get('elements') as FormArray;
}
'controls' isn't a property of an AbstractControl. instead you can use your form group, so 'dynamicForm.controls.controlName'

Problems with Angular Dynamic Reactive Form

I'm trying to create a dynamic reactive form. The user has the ability to choose between either a text (type = 1) input or img (type = 2) input. According to his choice, the right input is being added. - He can add as much input field as he wants.
I've never really used reactive forms before, hence this question.
The code below adds a control according to what module the user has chosen, but for instance adding a textarea only displays a textarea with [object Object] inside - clicking makes it disappear.
Additionally I haven't figured out yet how to submit the form's input. Logging form on submit returns the form, but without the textarea's input.
That's what I have so far:
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div *ngFor="let module of form.get('modules').controls; let i = index" class="position-relative" [style.padding-bottom.rem]="paddingValue" (mouseover)="showPaddingInput = true" formArrayName="modules">
<div class="padding-input position-absolute d-flex justify-content-center" *ngIf="showPaddingInput === true" [style.height.rem]="paddingValue">
<input type="text" class="align-self-center text-center padding-input-field" [value]="paddingValue" (input)="changePadding(padding.value)" #padding>
</div>
<div class="text" *ngIf="module.value.type === 1">
<textarea class="flow-text" placeholder="Some Text..." rows="3" [formControlName]="i"></textarea>
</div>
<div class="img-container" *ngIf="module.value.type === 2">
<div class="custom-file align-self-center">
<input type="file" id="i" class="custom-file-input" [formControlName]="i" (change)="handleFileInput($event.target.files)">
<label class="custom-file-label" for="i"></label>
</div>
</div>
</div>
<button class="btn btn-dark">Submit</button>
</form>
export class CreateCaseCmsComponent implements OnInit {
form: FormGroup;
constructor(private caseService: CasesService) { }
addModule(type) {
if (type === 1) {
const control = new FormControl({type: 1}, Validators.required);
(this.form.get('modules') as FormArray).push(control);
} else if (type === 2) {
const control = new FormControl({type: 2}, Validators.required);
(this.form.get('modules') as FormArray).push(control);
}
}
ngOnInit() {
this.form = new FormGroup({
modules: new FormArray([])
});
}
onSubmit() {
console.log(this.form);
}
}
the first argument to a form control is it's value, so you're setting the initial value as an object and that's why it's showing [object Object] in the text box... that's what you get if you call .toString() on an object, you need to instantiate them like this:
const control = new FormControl('', Validators.required);
or something like that... this affects how you're building your template, so you probably need something more like:
const group = new FormGroup({
type: new FormControl(1),
value: new FormControl('', Validators.required)
});
and add that group to your array and access it like:
<div class="text" *ngIf="module.get('type').value === 1" [formGroupName]="i">
<textarea class="flow-text" placeholder="Some Text..." rows="3" formControlName="value"></textarea>
</div>

Reactive form angular using FormArrayName

In my form I need to add phones, I have seen that in the database you are saving a string array with the phone numbers that I added, but the moment the value returned from the database is set to the form, only the first array value is shown .
I would like to display all array values that contain number of phones in string format.
component.html:
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="start">
<div formArrayName="phones" fxFlex="30">
<div *ngFor="let phone of phones.controls; index as i">
<mat-form-field>
<input matInput [formControlName]="i" placeholder="Phone Number">
</mat-form-field>
</div>
</div>
<div fxFlex="30">
<button mat-raised-button class="blue" (click)="addPhone()">Add</button>
</div>
</div>
component.ts:
this.myForm = this._formBuilder.group({
phones: new FormArray([new FormControl('')]),
});
this.values$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((values) => {
// values = {phones: ['999999', '88888', '77777']}
if (values) {
this.myForm.patchValue(values, { emitEvent: false });
}
});
get phones(): FormArray { return this.myForm.get('phones') as FormArray; }
addPhone(): void { this.phones.push(new FormControl('')); }
Even returning more than one value inside the array only displays the first array value on my form, what am I doing wrong?
patchValue won't create any form controls for you, so when you call patchValue it only sets the value for the first form control in your form array, because you only created one.
You have to call addPhone for each phone number or create form inside the describe when you already know how many form controls there should be, but you still need to create as many form controls as number of elements in your array.
this.myForm = this._formBuilder.group({
phones: new FormArray(),
});
this.values$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((values) => {
if (values && values.phones) {
phones.forEach(phone => this.addPhone(phone));
}
});
get phones(): FormArray { return this.myForm.get('phones') as FormArray; }
addPhone(phone = ''): void { this.phones.push(new FormControl(phone)); }

ReactiveForm for dynamically updated form entries

How to check status of myform in component to check if all the fields are filled or not?
HTML:
<div [formGroup]="myform">
<div *ngFor="let question of questions">
<label>{{question.question}}</label>
<select required >
<option selected disabled="disabled">Option</option>
<option *ngFor="let option of question['options']">{{option}}</option>
</select>
</div>
</div>
</div>
Question JSON coming from API:
this.questions = [
{
question: 'What is your age?',
options: ['20', '30', '40']
},
{
question: 'How much quantity?',
options: ['1','2','3']
}]
If you use ReactiveForm, you need use a FormArray
A FormArray can be of FormControl or a FormGroup
FormArray of FormControls
constructor(private fb:FormBuilder) {}
ngOnInit() {
//We create an array of FormControl, each question a FormControl
let data:FormControl[]=this.questions.map(q=>new FormControl());
this.myform=this.fb.group({
questions:new FormArray(data)
})
}
//the .html
<!--we use *ngIf to show the form only when we create the form-->
<div *ngIf="myform" [formGroup]="myform">
<!--we iterate to myForm.get('questions').controls -->
<!--we use our variable "questions" to show the label and options-->
<div *ngFor="let question of myform.get('questions').controls;let i=index">
<label>{{questions[i].question}}</label>
<select required [formControl]="question" >
<option value="null" disabled="disabled">Option</option>
<option *ngFor="let option of questions[i].options">{{option}}</option>
</select>
</div>
</div>
<!--just for check-->
{{myform?.value |json}}
If we use an array of formGroup we change some things
constructor(private fb:FormBuilder) {}
ngOnInit() {
//we create and array of FormGroup
let data2:FormGroup[]=this.questions.map(q=>this.fb.group({
option:null
}));
this.myform2=this.fb.group({
questions:new FormArray(data2)
})
}
<div *ngIf="myform2" [formGroup]="myform2">
<!--see that we say to Angular the "formArrayName" -->
<div formArrayName="questions">
<div *ngFor="let question of myform2.get('questions').controls;
let i=index" [formGroupName]="i"> <!--don't forget formGroupName-->
<label>{{questions[i].question}}</label>
<!--the select use formControlName, our array is an array of FormGroup-->
<select required formControlName="option" >
<option value="null" disabled="disabled">Option</option>
<option *ngFor="let option of questions[i].options">{{option}}</option>
</select>
</div>
</div>
</div>
{{myform2?.value |json}}
Aclaration:#FrontEndDeveloper. One thing is the array question that we use to make the questions.(Perhafs I must be choose other names to the variables), other thing is the value of the form. The value of myform1={questions:["20","1"]}, the value of myform2={questions:[{option:"20"},{option:"2"}]}.
When we create an array of FormControl (or an array of FbGroup) I used map, equally I can do some like
let data:FormControl[]=[];
data.push(new FormControl());
data.push(new FormControl());
or
let data2:FormGroup[]=[];
data2.push(this.fb.group({
option:null
}));
data2.push(this.fb.group({
option:null
}));
Generally we have some data to initialize the form. (an object with some data) that we get from a dbs
//Imagine we have mydata{name:"A",options=["20","1"]}
//we can map this data to create the form
let data:FormControl[]=this.mydata.options.map(q=>new FormControl(q));
//or
let data2:FormGroup[]=this.mydata.options.map(q=>this.fb.group({
option:q
}));
//Imagine we have mydata{name:"A",options=[{option:"20"},{option:"1"}]}
//we can map this data to create the form
let data:FormControl[]=this.mydata.options.map(q=>new FormControl(q.option));
//or
let data2:FormGroup[]=this.mydata.options.map(q=>this.fb.group({
option:q.option
}));
This would help you to understand reactive form basic functionalities.
https://stackblitz.com/edit/objects-equality-check-edhyk5?file=src/app/app.component.ts
It will help to understand:
1. FormBulder,
2. FormGroup,
3. Form Value changes etc.

How to use ReactiveFormsModule and validations for dynamically added controls in Angular?

I have a couple of fields connected to the FormBuilder like this.
constructor(private builder: FormBuilder) {
this.form = builder.group({
"firstName": [...],
"lastName": [...]
});
}
In the markup, besides the fix controls, I'll also have an unknown number of input boxes. It's starting with zero and can be increased, as the user clicks on a button, which adds a new input box by each click. There'll be also a removal button for each input box so the number of those is fully dynamic.
<div class="input-caption">FirstName</div>
<input type="text" formControlName="firstName">
<div class="input-caption">LastName</div>
<input type="text" formControlName="lastName">
<div id="options"></div>
<button (click)="addOption()">Add option</button>
As the dynamically added fields need to be validated, I feel that I need to declare them in the group definition. I have no idea how, though.
When I googled, I got a bunch of hits on [(ngModel)] but I also read that it's supposed to be used with the FormsModule and not ReactiveFormsModule as discussed in a blog.
How should I approach it? Is double-bound ngModel the only option?
Edit based on the answer.
HTML
<div *ngFor="let tag of []; let i = index;" [formGroupName]="i">
<div class="input-caption">Tag</div>
<input formControlName="name"> -->
</div>
TS
constructor(private builder: FormBuilder) {
this.form = builder.group({
"firstName": [...],
"lastName": [...],
"tags": this.builder.array([]),
});
}
addTag(): void {
(this.form.controls.tags as FormArray).???;
}
I've also tried to specify a hard-coded array in the markup as follows but didn't get to see any controls being rendered.
<div *ngFor="let tag of [{'name':'uno'},{'name':'duo'}]; let i = index;"
[formGroupName]="i">
<div class="input-caption">Tag</div>
<input formControlName="name"> -->
</div>
Your scenario is the purpose of a FormArray. See the docs here: https://angular.io/guide/reactive-forms#use-formarray-to-present-an-array-of-formgroups
The docs show an array of FormGroups, but you can also use it to create an array of FormControls.
Here is an example from my application:
this.productForm = this.fb.group({
productName: ['', [Validators.required,
Validators.minLength(3),
Validators.maxLength(50)]],
productCode: ['', Validators.required],
starRating: ['', NumberValidators.range(1, 5)],
tags: this.fb.array([]),
description: ''
});
My "tags" look like this:
I set default values into these controls like this:
// Update the data on the form
this.productForm.patchValue({
productName: this.product.productName,
productCode: this.product.productCode,
starRating: this.product.starRating,
description: this.product.description
});
this.productForm.setControl('tags', this.fb.array(this.product.tags || []));
You can find my sample code here: https://github.com/DeborahK/Angular2-ReactiveForms In the "APM" or "APM - Updated" folders.
This is the code from the Pluralsight course: Angular Reactive Forms here: https://app.pluralsight.com/library/courses/angular-2-reactive-forms/table-of-contents
If you just want this code to work, you just need to change it to an array:
<div *ngFor="let tag of ['uno','duo']; let i = index;"
[formGroupName]="i">
<div class="input-caption">{{ tag }}</div>
<input formControlName="name"> -->
</div>
And they don't really need to be form groups. It would probably be simpler if they aren't:
<div *ngFor="let tag of ['uno','duo']">
<div class="input-caption">{{ tag }}</div>
<input [formControlName]="tag"> -->
</div>
And if this is all you need ... then you don't really even need FormArrays.