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.
Related
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.
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
})
}
I'm working on an update task, I have a list of objects displayed in datatable and I want to execute the update process with a modal contain a form with an input and select options, when click the button to display the form, the input take the first attribute of my objectn but the problem is that the select option does not take a default value which is should be the second attribute of object !
<a data-toggle="modal" [attr.data-target]="'#modal-centered' + index"><i class="la la-pencil edit"
(click)="patchValue(rowData)"></i></a>
<div [attr.id]="'modal-centered' + index" class="modal fade">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{rowData.name}}</h4>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">×</span>
<span class="sr-only">Fermer</span>
</button>
</div>
<div class="modal-body">
<form [formGroup]="updateForm">
<div class="form-group">
<input type="text" formControlName="name" placeholder="Nom de la commission"
class="form-control">
</div>
<div class="form-group">
<select name="president" class="col-lg-6 custom-select form-control rounded"
formControlName="president">
<option [ngValue]="null" disabled>Choisir un Président</option>
<option [value]="president._id" *ngFor="let president of presidents">
{{president.firstName}}
{{president.lastName}}</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal"
(click)="updateCommission(rowData._id)">Sauvegarder</button>
</div>
</div>
</div>
</div>
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { FormGroup, FormControl } from '#angular/forms';
#Component({
selector: 'app-commission',
templateUrl: './commission.component.html',
styleUrls: ['./commission.component.css']
})
export class CommissionComponent implements OnInit {
commissions: any[];
updateForm: FormGroup;
presidents: any[];
cols: any[];
loading: boolean = true;
constructor(
private http: HttpClient
) {
this.updateForm = new FormGroup({
name: new FormControl(''),
president: new FormControl('')
});
}
ngOnInit() {
this.http.get('/api/commissions').subscribe((commissions: any[]) => {
this.loading = false
this.commissions = commissions;
})
this.http.get('/api/users/byType/conseillerMunicipal').subscribe((presidents: any[]) => {
this.presidents = presidents;
})
this.cols = [
{ field: 'name', header: 'Nom' },
{ field: 'presidentFullName', header: 'President' },
];
}
updateCommission(commissionID) {
this.loading = true;
this.http.put('/api/commissions/' + commissionID, this.updateForm.value).subscribe(updatedCommission => {
this.loading = false;
this.commissions.filter(commission => commission._id === commissionID)[0] = updatedCommission;
})
}
patchValue(commission) {
debugger
this.updateForm.setValue({
name: commission.name,
president: commission.presidentFullName
})
}
}
This does the job and displays the presidents in a drop down. However, I also need to select the president of the object to updated by default .
Try this one:
this.updateForm = new FormGroup({
name: new FormControl(''),
president: new FormControl(null)
});
Accoring to your condition inner template, you compare option with null. But your default value for formControl is ''.
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");
}
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