Sending input value with formArray values? - html

I have a FormArray that can input new fields and can send the value of the whole form on button click, however I am trying to add an input that is tied to the name held within the same object data, but I cannot seem to get it to display let along send with the rest of the updated data...
Here is my blitz
html
<form [formGroup]="myForm">
<div formArrayName="companies">
<!-- I am wanting to update and send the name of the input also... -->
<input formControlName="name"/>
<div *ngFor="let comp of myForm.get('companies').controls; let i=index">
<legend><h3>COMPANY {{i+1}}: </h3></legend>
<div [formGroupName]="i">
<div formArrayName="projects">
<div *ngFor="let project of comp.get('projects').controls; let j=index">
<legend><h4>PROJECT {{j+1}}</h4></legend>
<div [formGroupName]="j">
<label>Project Name:</label>
<input formControlName="projectName" /><span><button (click)="deleteProject(comp.controls.projects, j)">Delete Project</button></span>
</div>
</div>
<button (click)="addNewProject(comp.controls.projects)">Add new Project</button>
</div>
</div>
</div>
</div><br>
<button (click)="submit(myForm.value)">send</button>
</form>
.ts
export class AppComponent {
data = {
companies: [
{
name: "example company",
projects: [
{
projectName: "example project",
}
]
}
]
}
myForm: FormGroup;
constructor(private fb: FormBuilder) {
this.myForm = this.fb.group({
companies: this.fb.array([])
})
this.setCompanies();
}
addNewProject(control) {
control.push(
this.fb.group({
projectName: ['']
}))
}
deleteProject(control, index) {
control.removeAt(index)
}
setCompanies() {
let control = <FormArray>this.myForm.controls.companies;
this.data.companies.forEach(x => {
control.push(this.fb.group({
name: x.name,
projects: this.setProjects(x) }))
})
}
setProjects(x) {
let arr = new FormArray([])
x.projects.forEach(y => {
arr.push(this.fb.group({
projectName: y.projectName
}))
})
return arr;
}
submit(value) {
console.log(value);
}
}

Because you are using a controlArray you will need to move the input within the scope of the [formGroupName]="i" as formControlName="name" is a child of [formGroupName]="i".
<legend><h3>COMPANY {{i+1}}: </h3></legend>
<div [formGroupName]="i">
<input formControlName="name"/>

Related

Angular: http GET method is not working just after calling http POST, but its working when i enclose the GET method with setTimeout() method. Why?

I am new to Angular. Here, I was trying to add an object to a db and to display it in template simultaneously.
app.component.ts
export class AppComponent implements OnInit {
title = 'HttpRequest';
allProducts: Product[] = [];
#ViewChild('productsForm')
form!: NgForm;
constructor(private http: HttpClient) {}
ngOnInit() {
this.fetchProducts();
}
onProductsFetch() {
this.fetchProducts();
}
onProductCreate(products: { pName: string; desc: string; price: string }) {
console.log(products);
let header = new HttpHeaders({ myHeader: 'sachin' });
this.http
.post<{ name: string }>(
'*****',
JSON.stringify(products),
{ headers: header }
)
.subscribe({
next: (res) => {
// console.log(res);
},
});
//--------------------Error is here-----------------------------------------
//! This is not working
this.onProductsFetch();
//! This is working
// setTimeout(() => {
// this.onProductsFetch();
// }, 1000);
//--------------------Error is here-----------------------------------------
}
private fetchProducts() {
this.http
.get<{ [key: string]: Product }>(
'*****'
)
.pipe(
map((res) => {
let products: Product[] = [];
for (const [key, value] of Object.entries(res)) {
products.push({ ...value, id: key });
}
return products;
})
)
.subscribe({
next: (products) => {
this.allProducts = [...products];
console.log(this.allProducts);
},
});
}
}
app.component.html
<div class="main-area">
<div class="content-area">
<div class="header">
<h1>Manage Products</h1>
<hr />
</div>
<div class="container">
<!--Add product form-->
<div class="form-area">
<h3>Create Product</h3>
<form
#productsForm="ngForm"
(ngSubmit)="onProductCreate(productsForm.value)"
>
<label>Procuct Name</label>
<input type="text" name="pName" ngModel />
<label>Procuct Description</label>
<input type="text" name="desc" ngModel />
<label>Procuct Price</label>
<input type="text" name="price" ngModel />
<input type="submit" value="Add Product" />
</form>
</div>
<!--Display product area-->
<div class="product-display-area">
<h3>All Products</h3>
<table id="products">
<tr>
<th>#</th>
<th>Name</th>
<th>Description</th>
<th>Price</th>
<th></th>
<th></th>
</tr>
<tr *ngFor="let prod of allProducts; let i = index">
<td>{{ i + 1 }}</td>
<td>{{ prod.pName }}</td>
<td>{{ prod.desc }}</td>
<td>${{ prod.price }}</td>
</tr>
</table>
<hr />
<div class="action-btn-container">
<button class="btn-fetch" (click)="onProductsFetch()">
Refresh Products
</button>
</div>
</div>
</div>
</div>
</div>
products.model.ts
export class Product {
pName!: string;
desc!: string;
price!: string;
id?: string;
}
So here, when i use onProductCreate() method, the POST method is working but onProductFetch() isnt working and the template is not updating while if we use setTimeout(), its working completely and also template got updated. Why its happening like that?
PS: Please forgive me if my question is wrong :)
http.post just returns observable immidiately and does not wait your POST get response.
You have to put your this.onProductsFetch(); in subscribe.
this.http
.post<{ name: string }>(
'******',
JSON.stringify(products),
{ headers: header }
)
.subscribe({
next: (res) => {
// console.log(res);
this.onProductsFetch();
},
});

Angular: json to formBuilder to json

From my server I am receiving a json that contains questions and different options:
[
{"description":"what is a color","questionID":"1","options":[{"response":"blue","optionID":"1"},{"response":"red","optionID":"2"},{"response":"football","optionID":"3"}]},
{"description":"what is a sport","questionID":"2","options":[{"response":"working","optionID":"4"},{"response":"playing","optionID":"5"},{"response":"dad","optionID":"6"},{"response":"chess","optionID":"7"}]}
]
With the formbuilder I created a form for this:
If I press submit I would like to send this json to my server:
{
"answers": [{"questionID":"1", "selectedoptionIDS":[{"selectedOptionID":"2"}]},
{"questionID":"2", "selectedoptionIDS":[{"selectedOptionID":"1"},{"selectedOptionID":"3"}]}
],
"email": "test#test.com"
}
I know how I can build my form with the formbuilder but when I press submit I am having troubles with responding the right JSON. Certainly because I can not work with this checkboxes. Can somebody help me with this?
Html page
<form [formGroup]="examForm" (ngSubmit)="onSubmit(examForm.value)">
<div formArrayName="answers">
<div *ngFor="let question of questions; let i=index">
<label>{{i+1}}) {{question.description}}</label>
<br />
<div *ngFor="let response of question.options">
<input type="checkbox" value="response.optionID" />
{{response.response}}
</div>
</div>
</div>
<label>Email:</label>
<input class="form-control" id="email" type="text" formControlName="email">
<div class="block-content block-content-full block-content-sm bg-body-light font-size-sm">
<button class="btn btn-primary" type="submit">Submit</button>
</div>
</form>
TS Page
import { Component, OnInit } from '#angular/core';
import { ExamSimulatorService } from '../services/exam-simulator.service';
import { ActivatedRoute } from '#angular/router';
import { FormBuilder, FormArray } from '#angular/forms';
#Component({
selector: 'app-exam',
templateUrl: './exam.component.html'
})
export class ExamComponent implements OnInit {
software;
questions;
examForm;
constructor(
private examSimulatorService: ExamSimulatorService,
private formBuilder: FormBuilder
) {
this.examForm = this.formBuilder.group({
email: "",
answers: this.formBuilder.array([
this.initAnswer()])
})
}
buildForm() {
for (var i = 0; i < this.questions.length() + 1; i++) {
this.addAnswer();
}
}
initAnswer() {
return this.formBuilder.group({
questionID: "",
selectedOptionIDs: this.formBuilder.array([
this.initOptions()
])
})
}
initOptions() {
return this.formBuilder.group({
selectedOptionID: ""
})
}
addAnswer() {
const answers = <FormArray>this.examForm["controls"]["answers"];
answers.push(this.initAnswer())
console.log(this.examForm);
}
addOption(i) {
const options = <FormArray>this.examForm["controls"]["answers"]["controls"][i]["controls"]["selectedOptionIDs"]
options.push(this.initOptions())
}
ngOnInit() {
this.activatedRoute.paramMap
.subscribe(params => {
this.software = params['params']['software'];
this.examSimulatorService.getExam(this.software).subscribe(response =>
this.questions = response["questions"]["questionList"]);
})
setTimeout(() => this.buildForm(), 200)
}
onSubmit(values) {
//this.examSimulatorService.addQuestion(values).subscribe(
// (responses) => {
// console.log(responses);
// });
//this.options.clear();
console.log(values);
}
}
Instead of using your own model you can use full help from the Reactive form. The final model is not exactly you required but you can make workaround from it. You can see the working example at here https://stackblitz.com/edit/angular-1gtfmf
Component
export class ExamComponent implements OnInit {
#Input() name: string;
questionsList;
examForm: FormGroup;
dataModel: any; //active model
constructor(
private examSimulatorService: QuestionService,
private formBuilder: FormBuilder
) { }
get question(): FormGroup {
return this.formBuilder.group(
{
questionID: "",
description: "",
options: this.formBuilder.array([])
}
);
}
get option(): FormGroup {
return this.formBuilder.group({
optionID: "",
response: "",
selected: false
});
}
ngOnInit() {
this.dataModel = Object.create(null);
this.examForm = this.formBuilder.group({
email: ['', [Validators.required]],
questions: this.formBuilder.array([])
});
this.examSimulatorService.getAllQuestion().subscribe(response => {
this.questionsList = response.data;
this.loadForm(this.questionsList);
console.log(this.questionsList);
});
this.examForm.valueChanges.subscribe(data => {
this.dataModel = data;
});
}
loadForm(data) {
for (let ques = 0; ques < data.length; ques++) {
const questionsFormArray = this.examForm.get("questions") as FormArray;
questionsFormArray.push(this.question);
for (let opt = 0; opt < data[ques].options.length; opt++) {
const optionsFormsArray = questionsFormArray.at(ques).get("options") as FormArray;
optionsFormsArray.push(this.option);
}
}
this.examForm.controls.questions.patchValue(data);
}
showSavedValue() {
return this.dataModel;
}
showValue() {
return this.examForm.getRawValue();
}
onSubmit(values) {
console.log(values);
}
}
Html
<form [formGroup]="examForm" (ngSubmit)="onSubmit(examForm.value)">
<div>
<label>Email:</label>
<input class="form-control" id="email" type="text" formControlName="email">
</div>
<div formArrayName="questions">
<div *ngFor="let question of examForm.get('questions').controls;let questionIndex=index" [formGroupName]="questionIndex">
<label>{{questionIndex+1}} </label> {{examForm.value.questions[questionIndex].description}}
<div formArrayName="options">
<div *ngFor="let option of question.get('options').controls; let optionIndex=index" [formGroupName]="optionIndex">
<input type="checkbox" formControlName="selected" value="" /> {{examForm.value.questions[questionIndex].options[optionIndex].response}}
</div>
</div>
</div>
<div class="block-content block-content-full block-content-sm bg-body-light font-size-sm">
<button class="btn btn-primary" type="submit">Submit</button>
</div>
</div>
</form>
<pre> {{showSavedValue() | json }}
<pre>{{showValue() | json}}</pre>

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");
}

Angular 6: Cannot read property 'controls' of undefined - reactive forms

I am building a simple dashboard in Angular 6. I am using form builder to create my form that looks like this:
campaignForm = this.formBuilder.group({
name: ['', Validators.required],
countries: this.formBuilder.array([
this.formBuilder.control('')
]),
apps: this.formBuilder.array([
this.formBuilder.control('')
])
});
I would like to be able to save that form and save it via api to the database. However I keep getting 'Cannot read property 'controls' of undefined' that is pointing to the line in html. I've been following a guide in angular documentation on how to build reactive forms.
Here's the relevant TS code:
campaign.model.ts
export class Campaign {
id: string;
name: string;
countries?: any;
apps?: any;
}
add-campaign.component.ts
campaign: Campaign;
campaignForm = this.formBuilder.group({
name: ['', Validators.required],
countries: this.formBuilder.array([
this.formBuilder.control('')
]),
apps: this.formBuilder.array([
this.formBuilder.control('')
])
});
constructor(public campaignsService: CampaignsService, public route: ActivatedRoute, private formBuilder: FormBuilder) { }
ngOnInit() {
this.route.paramMap.subscribe((paramMap: ParamMap) => {
if (paramMap.has('campaignId')) {
this.mode = 'edit-campaign';
this.campaignId = paramMap.get('campaignId');
this.campaignsService.getCampaign(this.campaignId)
.subscribe(campaignData => {
this.campaign = {
id: campaignData._id,
name: campaignData.name,
countries: campaignData.countries,
apps: campaignData.apps
};
this.campaignForm.setValue({
'name': this.campaign.name,
'countries': this.campaign.countries
});
});
} else {
this.mode = 'add-campaign';
this.campaignId = null;
}
});
get country() {
return this.campaignForm.get('countries') as FormArray;
}
get app() {
return this.campaignForm.get('apps') as FormArray;
}
addCountry() {
this.campaign.countries.push(this.formBuilder.control(''));
}
addApp() {
this.campaign.apps.push(this.formBuilder.control(''));
}
add-campaign.component.html
<form [formGroup]="campaignForm" (ngSubmit)="onSaveCampaign($event)">
<label>Name</label>
<div>
<input type="text" formControlName="name" placeholder="name" required>
</div>
<div formArrayName="countries">
<h3>Country</h3>
<button type="button" (click)="addCountry()">Add Country</button>
<div *ngFor="let country of countries.controls; let i=index">
<label>
Country:
</label>
<input type="text" [formControlName]="i">
</div>
</div>
<div formArrayName="apps">
<h3>App</h3>
<button type="button" (click)="addApp()">Add App</button>
<div *ngFor="let app of apps?.controls; let i=index">
<label>
App
</label>
<input type="text" [formControlName]="i">
</div>
</div>
<button mat-raised-button color="primary" type="submit" [disabled]="!campaignForm.valid">Save campaign</button>
<button mat-raised-button color="primary" type="button" class="button-margin-left" routerLink="/">Go back</button>
</form>
the error seems to be pointing to this line:
<div *ngFor="let country of countries.controls; let i=index">
Does anyone have any idea why is this happening? It seems like Angular cannot access countries property, but I just do not know how to get around this. Any help would be appreciated.

Angular 6 reactive forms. FormArray of select

I followed Angular Reative Form guide that explains how to add a FormArray of Adrresses to a FormGroup.
Now I want to have a hero that can have different powers, selecting them from a select, or better from a dynamic array of select.
Passing from the example of Angular Docs to my desired functionality I can't make it to run.
This is my hero-form.ts
#Component({
selector: 'app-hero-form',
templateUrl: './hero-form.component.html',
styleUrls: ['./hero-form.component.css']
})
export class HeroFormComponent implements OnInit, OnChanges {
heroForm: FormGroup;
nameChangeLog: string[] = [];
hero: Hero = new Hero();
allPowers: Power[] = [];
constructor(private fb: FormBuilder, private powerService: PowerService) {
this.createForm();
this.logNameChange();
}
ngOnInit() {
this.powerService.getAll().subscribe(powers => this.allPowers = powers);
}
createForm() {
this.heroForm = this.fb.group({
name: ['', Validators.required],
powers: this.fb.array([]),
});
}
ngOnChanges() {
this.rebuildForm();
}
rebuildForm() {
this.heroForm.reset({
name: this.hero.name
});
this.setPowersControl(this.hero.powers);
}
setPowersControl(powers: Power[]) {
const powersFGs = powers.map(pow => this.fb.group(pow));
const powersFormArray = this.fb.array(powersFGs);
this.heroForm.setControl('powers', powersFormArray);
}
get powers(): FormArray {
const pows = this.heroForm.get('powers') as FormArray;
return pows;
}
addPowerChoice() {
this.powers.push(this.fb.control(new Power()));
// this.powers.push(this.fb.group(new Power(), Validators.required));
}
onSubmit() {
this.hero = this.prepareSaveHero();
console.log('SAVING HERO', this.hero);
// this.heroService.updateHero(this.hero).subscribe(/* error handling */);
this.rebuildForm();
}
prepareSaveHero(): Hero {
const formModel = this.heroForm.value;
// deep copy of form model lairs
const powersDeepCopy: Power[] = formModel.powers.map(
(pow: Power) => Object.assign({}, pow)
);
// return new `Hero` object containing a combination of original hero value(s)
// and deep copies of changed form model values
const saveHero: Hero = {
id: this.hero.id,
name: formModel.name as string,
// addresses: formModel.secretLairs // <-- bad!
powers: powersDeepCopy
};
return saveHero;
}
revert() { this.rebuildForm(); }
logNameChange() {
const nameControl = this.heroForm.get('name');
nameControl.valueChanges.forEach(
(value: string) => this.nameChangeLog.push(value)
);
}
}
This is my hero-form.html
<form [formGroup]="heroForm" (ngSubmit)="onSubmit()">
<div style="margin-bottom: 1em">
<button type="submit" [disabled]="heroForm.pristine" class="btn btn-success">Save
</button>
<button type="button" (click)="revert()" [disabled]="heroForm.pristine" class="btn btn-danger">Revert</button>
</div>
<!-- Hero Detail Controls -->
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div formArrayName="powers" class="well well-lg">
<div *ngFor="let pow of powers.controls; let i=index" [formControlName]="i">
<!-- The repeated power template -->
<h4>Potere #{{i + 1}}</h4>
<div style="margin-left: 1em;">
<div class="form-group">
<label class="center-block">Power:
<select class="form-control">
<option *ngFor="let pow of allPowers" [value]="pow">{{pow.name}}</option>
</select>
</label>
</div>
</div>
<br>
<!-- End of the repeated address template -->
</div>
<button (click)="addPowerChoice()" type="button">Add a Power</button>
</div>
</form>
<p>heroForm value: {{ heroForm.value | json}}</p>
<h4>Name change log</h4>
<div *ngFor="let name of nameChangeLog">{{name}}</div>
This is power-service that is only returning stubbed data
#Injectable({
providedIn: 'root'
})
export class PowerService {
constructor() {
}
getAll(): Observable<Power[]> {
return of(this.getProds());
}
getProds(): Power[] {
const powers = [];
for (let i = 0; i < 10; i++) {
const pow = new Power();
pow.id = i+'';
pow.name = 'Power ' + i;
powers.push(pow);
}
return powers;
}
}
And these are my data models
export class Hero {
id: number;
name: string;
powers: Power[];
}
export class Power {
id: string = '';
name: string = '';
}
Here I have make a stackblitz example but it's not working
I've solved
I have moved formControlName from div onto select as suggested by Lucas Klaassen, and changed [value] to [ngValue] onto option
<form [formGroup]="heroForm" (ngSubmit)="onSubmit()">
<div style="margin-bottom: 1em">
<button type="submit"
[disabled]="heroForm.pristine" class="btn btn-success">Save
</button>
<button type="button" (click)="revert()"
[disabled]="heroForm.pristine" class="btn btn-danger">Revert
</button>
</div>
<!-- Hero Detail Controls -->
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div formArrayName="powers" class="well well-lg">
<div *ngFor="let pow of powers.controls; let i=index">
<!-- The repeated power template -->
<h4>Potere #{{i + 1}}</h4>
<div style="margin-left: 1em;">
<div class="form-group">
<label class="center-block">Power:
<select class="form-control" [formControlName]="i">
<option *ngFor="let pow of allPowers" [ngValue]="pow">{{pow.name}}</option>
</select>
</label>
</div>
</div>
<br>
<!-- End of the repeated address template -->
</div>
<button (click)="addPowerChoice()" type="button">Add a Power</button>
</div>
</form>
<p>heroForm value: {{ heroForm.value | json}}</p>
<h4>Name change log</h4>
<div *ngFor="let name of nameChangeLog">{{name}}</div>
Then I have changed onSubmit() adding a Hero's constructor call as follow
onSubmit() {
this.hero = this.prepareSaveHero();
console.log('SAVING HERO', this.hero);
// this.heroService.updateHero(this.hero).subscribe(/* error handling */);
this.hero = new Hero();
this.rebuildForm();
}
Then I have added a custom constructor to Hero class
export class Hero {
id: number;
name: string;
powers: Power[];
constructor() {
this.id = 0;
this.name = '';
this.powers = [];
}
}
Now it's working and correctly rebuilding form after submit