Angular FormArray - html

I m not able to access the FormArray in Angular 13.3. its showing this error in console. I have one form group, inside that I have 2 more form groups and 1 form array.
core.mjs:6485 ERROR Error: Cannot find control with name: 'third'
Here is my HTML code:
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div [formGroup]="bankFormGroup">
<input type="text" placeholder="property11" formControlName="property11">
<div *ngIf="bankFormGroup.get('property11')?.invalid && (bankFormGroup.get('property11')?.dirty || bankFormGroup.get('property11')?.touched)" class="alert">
<div *ngIf="bankFormGroup.get('property11')?.errors?.['required']">
Required.
</div>
</div>
<div [formGroup]="bankForm2Group">
<input type="text" placeholder="property21" formControlName="property21">
<div *ngIf="bankForm2Group.get('property21')?.invalid && (bankForm2Group.get('property21')?.dirty || bankForm2Group.get('property21')?.touched)" class="alert">
<div *ngIf="bankForm2Group.get('property21')?.errors?.['required']">
Required.
</div>
</div>
</div>
<ul class="list-group">
<li class="list-group-item" formArrayName="third" *ngFor="let product of bankForm3Group.controls; let i = index;">
<div [formGroupName]="i" class="row">
<div class="col-4">
<input type="text" formControlName="property3" class="form-control" id="property3" placeholder="property3">
</div>
</div>
</li>
</ul>
</div>
<div [formGroup]="bankFormGroup">
<input type="text" placeholder="property12" formControlName="property12">
</div>
<button type="submit">Submit</button>
</form>
TS code:
declare var $: any;
import { Component, OnInit } from '#angular/core';
import { FormArray, FormControl, FormGroup, Validators } from '#angular/forms';
#Component({
selector: 'app-first',
templateUrl: './first.component.html',
styleUrls: ['./first.component.css']
})
export class FirstComponent implements OnInit {
form!: FormGroup;
constructor() {
this.form = new FormGroup({
first: new FormGroup({
property11: new FormControl('property 1.1', Validators.required),
property12: new FormControl('property 1.2', Validators.required)
}),
second: new FormGroup({
property21: new FormControl('property 2.1', Validators.required)
}),
third: new FormArray([
new FormGroup({
property3: new FormControl('property 3')
}),
new FormGroup({
property3: new FormControl('property 3')
}),
])
});
}
get bankFormGroup(): FormGroup {
return this.form?.get('first') as FormGroup;
}
get bankForm2Group(): FormGroup {
return this.form?.get('second') as FormGroup;
}
get bankForm3Group(): FormArray {
return this.form?.get('third') as FormArray;
}
//get third(): FormArray {
// return this.form?.get('third') as FormArray;
//}
onSubmit() {
console.log('Submit', this.form.value);
}
ngOnInit(): void {
$(".preloader").hide();
}
}
I have separate FormGroup in TS but in HTML its nested. I create Getter Methods which resolved almost. but I m not able to access FormArray by this. I spent lot of time but no luck. Thanks in advance.

The main problem is that your "divs" are closed wrong. In this stackblitz I ordered the div of your .html.
NOTE 1: You can use formGroupName="first" and formGroupName="second" instead of [formGroup]="bankFormGroup" and [formGroup]="backForm2Group"
NOTE2: You can use the way form.get('formgroupname.formControlName') in your *ngIf,e.g.
<div *ngIf="form.get('second.property21')?.invalid>...</div>
NOTE3:I like more that the formArrayName="third" was in the <ul> instead of the <li> with the *ngFor (but it is a personal opinion)
NOTE4: For me is strange in the .html that you show the inputs "property 1.1" (from the formGroup "first") and "property 1.2" (from the formGroup "second"), and the last input the input "property 1.2" (from the formGroup "first")

Thank you #Eliseo, I have achieved my requirement. I have used <ng-container> to segregate the form group and their elements. By this approach I no longer needed getter methods too. Here is my updated code
HTML:
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<!--classess removed for clarity-->
<!--parent div contains first and second group. ng container has been used for each group-->
<div>
<!--accessing first group-->
<ng-container formGroupName="first">
<input type="text" placeholder="property11" formControlName="property11">
<div *ngIf="form.get('first.property11')?.invalid && (form.get('first.property11')?.dirty || form.get('first.property11')?.touched)" class="alert">
<div *ngIf="form.get('first.property11')?.errors?.['required']">
Required.
</div>
</div>
</ng-container>
<!--classess removed for clarity-->
<div>
<!--accessing second group-->
<ng-container formGroupName="second">
<input type="text" placeholder="property21" formControlName="property21">
<div *ngIf="form.get('second.property21')?.invalid && (form.get('second.property21')?.dirty || form.get('second.property21')?.touched)" class="alert">
<div *ngIf="form.get('second.property21')?.errors?.['required']">
Required.
</div>
</div>
</ng-container>
</div>
<ul>
<!--accessing third array-->
<ng-container formArrayName="third">
<li *ngFor="let product of third.controls; let i = index;">
<div [formGroupName]="i">
<div>
<input type="text" formControlName="property3" id="property3" placeholder="property3">
</div>
</div>
</li>
</ng-container>
</ul>
</div>
<!--classess removed for clarity-->
<div>
<!--accessing first group back again-->
<ng-container formGroupName="first">
<input type="text" placeholder="property12" formControlName="property12">
</ng-container>
</div>
<button type="submit">Submit</button>
</form>
TS Code:
declare var $: any;
import { Component, OnInit } from '#angular/core';
import { FormArray, FormControl, FormGroup, Validators } from '#angular/forms';
#Component({
selector: 'app-first',
templateUrl: './first.component.html',
styleUrls: ['./first.component.css']
})
export class FirstComponent implements OnInit {
form!: FormGroup;
constructor() {
this.form = new FormGroup({
first: new FormGroup({
property11: new FormControl('property 1.1', Validators.required),
property12: new FormControl('property 1.2', Validators.required)
}),
second: new FormGroup({
property21: new FormControl('property 2.1', Validators.required)
}),
third: new FormArray([
new FormGroup({
property3: new FormControl('property 3')
}),
new FormGroup({
property3: new FormControl('property 3')
}),
])
});
}
get third(): FormArray {
return this.form?.get('third') as FormArray;
}
onSubmit() {
console.log('Submit', this.form.value);
}
ngOnInit(): void {
$(".preloader").hide();
}
}
If there is any room of improvement then please let me know. I will update the code.

Related

form fields in angular not displaying already populated data when trying to create an update function

I am creating a MEAN application that can perform CRUD operations, however when i am creating the update function the form fields are not populated with the already existing data to be updated.
HTML:
<button
mat-button
color="basic"
[routerLink]="['/moc-report', mocreport._id]"
>
Update Status
</button>
This is the button but to update but the form does not contain the data needed to update:
HTML of the form to contain the data to be updated:
<div class="container">
<!--Navbar-->
<mat-toolbar color="primary">
<div class="centerNavBar">
<a mat-button href="fa-dashboard">Back</a>
<span class="headSpacer">Damage Assessment Tool</span>
<a mat-button href="">Logout</a>
</div>
</mat-toolbar>
</div>
<div>
<mat-sidenav-container class="MainContainter">
<!--SideNav-->
<mat-sidenav mode="side" opened>
<div>
<a mat-button href="">Message Board</a>
</div>
</mat-sidenav>
<mat-sidenav-content class="MainContent">
<mat-card>
<mat-card-header class="sect">Create report:</mat-card-header>
<br /><br />
<mat-card-content class="centerAlign">
<!--Div for form-->
<div>
<form [formGroup]="form" (submit)="addMOCForm()">
<mat-form-field class="formwidth">
<input
matInput
class="form-control"
formControlName="MoCReportDateTime"
type="Date"
required
/>
</mat-form-field>
<br />
<mat-form-field>
<mat-label>Comment</mat-label>
<input
placeholder="--"
matInput
formControlName="MoCDescription"
class="form-control"
type="string"
required
/>
</mat-form-field>
<br />
<mat-form-field>
<mat-label>Facility In Question</mat-label>
<input
placeholder="--"
matInput
formControlName="facilityName"
class="form-control"
type="string"
required
/>
</mat-form-field>
<mat-card class="centerAlign">
<mat-card-actions>
<!--Div for buttons-->
<div>
<input
style="display: none"
#ImageInput
type="file"
(change)="onFileSelected($event)"
/>
<button
mat-raised-button
type="button"
(click)="ImageInput.click()"
>
Upload Images
</button>
<button mat-raised-button color="primary" type="submit">
Add
</button>
</div>
</mat-card-actions>
</mat-card>
</form>
</div>
</mat-card-content>
</mat-card>
</mat-sidenav-content>
</mat-sidenav-container>
</div>
TS:
import { AfterViewInit, Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import {
FormBuilder,
FormGroup,
FormControl,
} from '#angular/forms';
import { HttpClient } from '#angular/common/http';
import { MOCReportService } from 'src/app/service/mocreport.service';
#Component({
selector: 'app-moc-report',
templateUrl: './moc-report.component.html',
styleUrls: ['./moc-report.component.css'],
})
export class MocReportComponent implements OnInit {
image: any;
Image = [];
imageData: any;
constructor(
private mocreportservice: MOCReportService,
//private mapService: MocMapService,
private router: Router,
private fb: FormBuilder,
private http: HttpClient
) {}
form = new FormGroup({
facilityName: new FormControl(''),
MoCDescription: new FormControl(''),
MoCReportDateTime: new FormControl(''),
});
onFileSelected(event: any) {
const file = (event.target as HTMLInputElement).files;
this.form.patchValue({ Image: file });
const allowedMimeTypes = ['image/png', 'image/jpeg', 'image/jpg'];
{
const reader = new FileReader();
reader.onload = () => {
this.imageData = reader.result as string;
};
if (file) {
reader.readAsDataURL(file[0]);
}
}
console.log(event.target.files[0]);
const Image = event.target.files[0];
this.image = Image;
}
addMOCForm() {
console.log('adding');this.MoCReportDateTime, this.mocImage);
const formData = new FormData();
formData.append('facilityName', this.form.value.facilityName);
formData.append('MoCDescription', this.form.value.MoCDescription);
formData.append(
'MoCReportDateTimeString',
this.form.value.MoCReportDateTimeString
);
formData.append('mocImage', this.image);
this.mocreportservice.postMOCForm(formData).subscribe((d) => {
console.log(d);
});
this.router.navigate(['/message-board']);
}
ngOnInit(): void {}
}
your question is missing two things - 1.code to update the form values is missing. 2.what values you want to update in the form and where are they in the code snippet provided above.
Still I am providing you the solution
1.First update your html and add a click event which will be used for updating the form on click of button
HTML:
<button
mat-button
color="basic"
[routerLink]="['/moc-report', mocreport._id]"
(click)="onUpdateClik()">
Update Status
</button>
TS:
onUpdateClik(){
//I'm assuming you want to update the form values which you are getting in form from the template.
//If you are getting the form values from any API then append those values and you'll be done.
this.form.patchValue({
facilityName: this.form.value.facilityName, //update your respective values
MoCDescription: this.form.value.MoCDescription, //update your respective values
MoCReportDateTime: this.form.value.MoCReportDateTimeString, //update your respective values
})
}

How to display a form after clicking a button in Angular 9?

I have a button on my page and when I click it I want my form to display right below it.
I also want the number of times I click the button, it should show that many forms.
I am new to Angular 9 and have no clue how to display a form on the same page as the button.
Code for the button:
<html>
<body ng-app>
<button type="submit" (click)="onClickForm" > Next </button>
</body>
</html>
Code for the form:
<html>
<form>
First Name: <input type="text" ng-model="firstname">
</form>
THE SOLUTION IS FOR ANGULARJS
in view
<html>
<body ng-app>
<button type="submit" ng-click="onClickForm()" ng-show="!showForm">
Next
</button>
<form ng-show="showForm">
First Name: <input type="text" ng-model="firstname">
</form>
</body>
</html>
in js Controller
$scope.showForm= false;
$scope.onClickForm = function(){
$scope.showForm = true;
}
I think this is what you are looking for.
You can use Reactive Forms and FormArray if you want a array of First Names or users. you can find a clear tutorial here. working StackBlitz can be found here.
in your ts file
import { Component, VERSION,OnInit } from '#angular/core';
import { FormBuilder, FormGroup, FormArray } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
name = 'Angular ' + VERSION.major;
userForm: FormGroup;
users: FormArray
constructor(private fb: FormBuilder){}
ngOnInit(): void{
this.userForm = this.fb.group({
users: this.fb.array([])
})
}
createUserForm(){
return this.fb.group({
firstname: ''
})
}
adduser(){
this.users = this.userForm.get('users') as FormArray;
this.users.push(this.createUserForm());
}
}
in your html file
<button (click)="adduser()"> next </button>
<form [formGroup]="userForm">
<div formArrayName="users" *ngFor="let item of userForm.get('users')['controls']; let i = index;">
<div [formGroupName]="i">
<input formControlName="firstname" placeholder="firstname">
</div>
</div>
</form>

ERROR Error: Cannot find control with path: 'stocks -> stockName'

Hi I have a error trying to create dynamic form with reactive form module
Here is my code for html
<div class="container-fluid">
<h2>Stock Rebalancer</h2>
<div class="form-group">
<input class="form-control" placeholder="Total Capital" [(ngModel)]="buyingPower">
</div>
<div class="form-group">
<input class="form-control" placeholder="Buying power" [(ngModel)]="buyingPower">
</div>
<form [formGroup]="stockForm">
<div formArrayName="stocks">
<div class="form-group" *ngFor="let stock of stockForm.get('stocks')['controls']; let i = index">
<div class="form-row">
<div class="col">
<input formControlName="stockName" class="form-control" placeholder="stock name">
</div>
<div class="col">
<input formControlName="currentTotal" class="form-control" placeholder="$current total">
</div>
<div class="col">
<input formControlName="stockPercentage" class="form-control" placeholder="percantage%">
</div>
<div class="col">
<input formControlName="stockPrice" class="form-control" placeholder="$stock price">
</div>
<button class="btn btn-light" type="button" title="remove Stock" (click)="onRemoveStock(i)">X</button>
</div>
</div>
</div>
</form>
<button class="btn btn-primary" type="button" title="Add Stock" (click)="addStock()">Add Stock</button>
<!-- <button class="btn btn-primary" type="button" title="Add Stock" (click)="onRebalance()">Rebalance</button> -->
</div>
It has four controls, stockName, stockPrice, currentTotal, and stockPercentage. With four of these controls can build an item in a form array.
Below is the code for component
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, FormArray, FormControl } from '#angular/forms';
import { Plan } from 'src/app/interface/plan';
#Component({
selector: 'app-stock-component-calculator',
templateUrl: './stock-component-calculator.component.html',
styleUrls: ['./stock-component-calculator.component.css']
})
export class StockComponentCalculatorComponent implements OnInit {
constructor(private formBuilder: FormBuilder) { }
stockForm: FormGroup;
buyingPower: number;
plans: Plan[]
ngOnInit(): void {
this.stockForm = this.formBuilder.group({
stocks: this.formBuilder.array([])
});
const stockControl = this.stockForm.get('stocks')['controls'];
for (var i = 0; i < 6; i++) {
stockControl.push(this.formBuilder.group({
stockName: new FormControl(''),
currentTotal: new FormControl(0),
stockPercentage: new FormControl(0),
stockPrice: new FormControl(0),
}));
}
}
createStock(): FormGroup {
return this.formBuilder.group({
stockName: new FormControl(''),
currentTotal: new FormControl(0),
stockPercentage: new FormControl(0),
stockPrice: new FormControl(0),
});
}
addStock() {
const stockControl = this.stockForm.get('stocks')['controls'];
stockControl.push(this.createStock());
}
onRemoveStock(index) {
const stockControl = this.stockForm.get('stocks')['controls'];
stockControl.removeAt(index);
}
}
I got the error as following:
ERROR Error: Cannot find control with path: 'stocks -> stockName'
ERROR Error: Cannot find control with path: 'stocks -> currentTotal'
I am wondering what is the mistake that I made here.
Thank you for your help!
I fixed my issue with changing the
*ngFor="let stock of stockForm.get('stocks')['controls']; let i = index"
to
*ngFor="let stock of stocks; let i = index"
Also adding a new div of
<div [formGroup]="stock">
for the ts part, I declared
stocks: FormGroup[]
this.stocks = <FormGroup[]>(this.stockForm.controls.stocks as FormArray).controls;
Since it is a nested structure FormGroup -> FormArray -> FormGroup, so it takes me a bit of time to figure out.

How to validate multiple preselected checkbox with angular 8 reactive form

I have few checkboxes whose values are coming from loop,Here I am validating those checkboxes using reactive form.My validation is atleast one checkboxes should be selected.when I check and uncheck the checkbox validation is working fine,but when my all checkboxes are already preselected and click submit,even though its showing empty message.Is there any solution for it.Here is the code below.
home.component.html
<div>
<p>Form 1</p>
<form [formGroup]="registerForm">
<div *ngFor="let grpdata of statusdata">
<input type="checkbox" formControlName="title" value="{{grpdata.groupid}}" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.title.errors }">{{grpdata.groupname}}<br>
</div>
<div *ngIf="submitted && f.title.errors" class="invalid-feedback">
<div *ngIf="f.title.errors.required">Title is required</div>
</div>
<button type="submit" (click)="getSelectedVal()">Click here</button>
</form>
</div>
<div>
<p>Form 2</p>
<form [formGroup]="editForm">
<input type="textbox" disabled formControlName="edithidden" [(ngModel)]="hello" class="form-control"><br>
<div *ngFor="let grpdata of statusdata">
<input type="checkbox" formControlName="edittitle" [checked]=true value="{{grpdata.groupid}}" class="form-control" [ngClass]="{ 'is-invalid': submitted1 && g.edittitle.errors }">{{grpdata.groupname}}<br>
</div>
<div *ngIf="submitted1 && g.edittitle.errors" class="invalid-feedback">
<div *ngIf="g.edittitle.errors.required">Title is required</div>
</div>
<button type="submit" (click)="editSelectedVal()">Click here</button>
</form>
</div>
home.component.ts
import { Component, OnInit } from '#angular/core';
import { CommonserviceService } from './../utilities/services/commonservice.service';
import { FormBuilder, FormControl, FormGroup, Validators } from '#angular/forms';
declare var $: any;
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
submitted = false;
submitted1 = false;
getListData: any;
registerForm: FormGroup;
editForm: FormGroup;
statusdata: any;
constructor(private commonserviceService: CommonserviceService,private formBuilder: FormBuilder)
{
this.registerForm = this.formBuilder.group({
title: [false, Validators.requiredTrue],
});
this.editForm = this.formBuilder.group({
edittitle: [false, Validators.requiredTrue],
edithidden: new FormControl()
});
}
ngOnInit() {
this.statusdata = [{"groupid":1,"groupname":"project1"},{"groupid":2,"groupname":"project2"},{"groupid":3,"groupname":"project3"}];
}
get f() { return this.registerForm.controls; }
get g() { return this.editForm.controls; }
getSelectedVal(){
this.submitted = true;
// stop here if form is invalid
if (this.registerForm.invalid) {
return;
}
console.log('submitted');
}
editSelectedVal(){
this.submitted1 = true;
// stop here if form is invalid
if (this.editForm.invalid) {
return;
}
console.log('submitted edit');
}
}
<input type="checkbox" formControlName="edittitle" [checked]=true...
You shouldn't try to set the value from outside of the form. You never know when it is actually attached. When you want to have the checkbox to be preselected use the form value instead.
this.editForm = this.formBuilder.group({
edittitle: [true, Validators.requiredTrue], // true here, you had false here
edithidden: new FormControl()
});

How to send dynamically added row values from angular to java controller

I created a dynamically adding/deleting rows using Angular reactive forms. Rows are getting added and deleted. But how you can send all these row values from Angular application to java controller.I am pasting the code below.
app.component.html
<div class="container">
<h3 class="page-header">Seasons</h3>
<button type="button" class="btn btn-primary" (click)="addTeam()">Add New Row</button><br/>
<form [formGroup] = "seasonsForm">
<div formArrayName = "teamRows">
<div *ngFor = "let team of seasonsForm.controls.teamRows.controls; let i=index" [formGroupName] = "i">
<h4>Team- #{{i+1}}</h4>
<div class="form-group">
<label>Team Name</label>
<input formControlName = "teamName" class="form-control">
</div>
<div class="form-group">
<label>Stadium</label>
<input formControlName = "stadiumName" class="form-control">
</div>
<div class="form-group">
<label>Capacity</label>
<input formControlName = "capacity" class="form-control">
</div>
<div class="form-group">
<label>Founded</label>
<input formControlName = "founded" class="form-control">
</div>
<div class="form-group">
<label>Head Coach</label>
<input formControlName = "headCoach" class="form-control">
</div>
<div class="form-group">
<label>Last Season</label>
<input formControlName = "lastSeason" class="form-control">
</div>
<button *ngIf = "seasonsForm.controls.teamRows.controls.length" (click) = "deleteTeam(i)" class="btn btn-danger">Delete Button</button>
</div>
</div>
</form>
<pre>{{ seasonsForm.value |json }}</pre>
</div>
app.component.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup,FormArray,FormBuilder,Validators} from '#angular/forms';
import { Teams } from '../service/http-client.service';
#Component({
selector: 'app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
public seasonsForm: FormGroup;
public teams:Teams[];
constructor(private _fb: FormBuilder) { }
ngOnInit() {
this.seasonsForm = this._fb.group({
teamRows: this._fb.array([this.initTeamRows()])
});
}
get formArr() {
return this.seasonsForm.get('teamRows') as FormArray;
}
initTeamRows() {
return this._fb.group({
teamName: [''],
stadiumName: [''],
capacity: [''],
founded: [''],
headCoach: [''],
lastSeason: ['']
});
}
addTeam() {
this.formArr.push(this.initTeamRows());
}
deleteTeam(index: number) {
this.formArr.removeAt(index);
}
}
createTeam(): void {
this.httpClient.post<Teams>("http://localhost:8080/addTeam", seasonsForm);
.subscribe( res => {
alert("Successful");
})
};
export class Teams {
constructor(
public teamName:string,
public stadiumName:string,
public capacity:string,
public founded:string,
public headCoach:string,
public lastSeason:string,
) {}
}
I tried to send entire formGroup(seasonsForm) but it is getting failed.I am relatively new to Angular and i searched in Google but i didn't find much help. So any help in this would be appreciated.
you need to send form value on your createTeam function. If you console.log(seasonsForm), you can see there are some other attributes which is only about your form.
Also if you want you can check is form valid.
createTeam(): void {
if(seasonsForm.valid){
this.httpClient.post<Teams>("http://localhost:8080/addTeam", seasonsForm.value);
.subscribe( res => {
alert("Successful");
})
};
}
First of all if you are using NoSQL database then you can send a JSON file containing arrays. So here i decided to send a json file containing Array values to server side and then i converted the form group name to JSON.Stringify. In Server side you can retrieve that json in String format and parse it and send to DB. Here is the code below
onSubmit() {
this.httpClient.post("http://localhost:8080/addTeams",JSON.stringify(this.seasonsForm.value))
.subscribe((response: Response) => {
})
alert("Successful");
}