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 ''.
Related
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.
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");
}
My project contains 4 components: Course-List, Course-Detail, Course-Play, Course Quiz, and the scheme is like that:
The data is pass like that: every course have array of segment and every segments have array of questions. All the data I get from a backend server (Ruby on Rails API project) and it pass correctly.
This is the interfaces of each data structure:
export interface ICourse {
id: number;
title: string;
autor: string;
segments: ISegment[];
}
export interface ISegment {
id: number;
unit_id: number;
unit_title: string;
name: string;
type: string;
data: string;
questions: IQuestion[];
}
export interface IQuestion {
id: number;
question: string;
answer1: string;
answer2: string;
answer3: string;
answer4: string;
correct: number;
}
I'm having a problem with course-quiz component.
This is how the quiz looks:
Problems:
When click submit and next the data's not saving
I want to add option of coloring the correct answer and user answer after press submit
I want that when all questions are answered that new page show instead (not working probably because the first)
This is the project in stackblitz. The project not working because stackblitz isn't working with sass. I'll add relevant code also here:
app-routing.module.ts
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { CourseListComponent } from './courses/course-list/course-list.component';
import { CourseDetailComponent } from './courses/course-detail/course-detail.component';
import { CoursePlayComponent } from './courses/course-play/course-play.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { CourseQuizComponent } from './courses/course-play/course-quiz/course-quiz.component';
// Routing array - set routes to each html page
const appRoutes: Routes = [
{ path: '', redirectTo: '/courses', pathMatch: 'full', runGuardsAndResolvers: 'always' },
{ path: 'courses', component: CourseListComponent, pathMatch: 'full', runGuardsAndResolvers: 'always' },
{ path: 'courses/:id', component: CourseDetailComponent, pathMatch: 'full', runGuardsAndResolvers: 'always' },
{ path: 'courses/:id/segments/:id', component: CoursePlayComponent, pathMatch: 'full', runGuardsAndResolvers: 'always',
children: [{ path: 'questions/:id', component: CourseQuizComponent }]
},
{ path: '**', component: PageNotFoundComponent, pathMatch: 'full', runGuardsAndResolvers: 'always' }];
#NgModule({
imports: [RouterModule.forRoot(appRoutes, { onSameUrlNavigation: 'reload' })],
exports: [RouterModule]
})
export class AppRoutingModule { }
course-play.component.ts
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute, Router, Routes, NavigationEnd } from '#angular/router';
import { MatSidenavModule } from '#angular/material/sidenav';
import { LocalStorage } from '#ngx-pwa/local-storage';
import { DomSanitizer } from '#angular/platform-browser';
import { ICourse } from '../course';
import { ISegment } from '../course';
import { CourseService } from '../course.service';
// Couse-play decorator
#Component({
selector: 'lg-course-play-course-play',
templateUrl: './course-play.component.html',
styleUrls: ['./course-play.component.sass']
})
export class CoursePlayComponent implements OnInit {
errorMessage: string;
course: ICourse;
courseId: number;
public currentSegment: ISegment;
public showChildren: boolean = false;
constructor(private courseService: CourseService,
private route: ActivatedRoute,
private router: Router,
public sanitizer: DomSanitizer) { }
ngOnInit() {
// save this course id from course-detail and get http request from the service
this.courseId = JSON.parse(localStorage.getItem("courseId"))
this.getCourse(this.courseId);
}
// Get course detail by id
getCourse(id: number) {
this.courseService.getCourse(this.courseId).subscribe(
course => {
this.course = course;
// get the current segment id to use it on the html file
const id = +this.route.snapshot.paramMap.get('id');
this.getCurrentSegment(id);
},
error => this.errorMessage = <any>error);
}
// search in course single segment by id and save it in currentSegment
// to use it in the html file
getCurrentSegment(id: number){
this.currentSegment = this.course.segments.find(d => d.id === id);
}
changeShowChildren() {
this.showChildren = !this.showChildren;
}
}
course-play.component.html
<div class="row content" *ngIf="course">
<!-- Side nav-bar -->
<div class="col-md-3">
<!-- Image Logo -->
<div id="head_sidebar">
<img src="./assets/images/lg-white.png" class="d-inline-block align-top logo" alt="" routerLink="/courses" style="outline: none">
<h3>{{course.title}}</h3>
</div>
<div class="col-md-12 sidenav">
<!-- Menu elemets -->
<div class="nav nav-pills nav-stacked" *ngFor="let unit of course.segments | groupBy: 'unit_title'; let i = index">
<h6 class="course_play_title">Unit {{ i+1 }}: {{ unit.key }} </h6>
<ul>
<li class="course_play_item" *ngFor="let lesson of unit.value">
<a class="nav-link" [routerLink]="['/courses', course.id, 'segments', lesson.id]" (click)=getCurrentSegment(lesson.id)>{{lesson.name}}</a>
</li>
</ul>
</div>
</div>
</div>
<!-- Body -->
<div class="col-md-9 no-gutters" *ngIf="currentSegment">
<!-- Video Div -->
<div class="col-md-12 course_play_body text-center" *ngIf="currentSegment.segment_type === 'Video'">
<h1>{{currentSegment.name}}</h1>
<p class="small-text" *ngIf="course.segments?.length > 0">lesson {{currentSegment.id}} of {{course.segments?.length}}</p>
<hr>
<iframe frameborder="0" allowfullscreen="true" [src]='currentSegment.data | safe'></iframe>
<button type="button" class="prev btn btn-primary btn-lg" *ngIf="currentSegment.id > 1" [routerLink]="['/courses', course.id, 'segments', currentSegment.id-1]" (click)=getCurrentSegment(currentSegment.id-1)>Previous</button>
<button type="button" class="next btn btn-primary btn-lg" *ngIf="currentSegment.id < course.segments?.length" [routerLink]="['/courses', course.id, 'segments', currentSegment.id+1]" (click)=getCurrentSegment(currentSegment.id+1)>Next</button>
</div>
<!-- Quiz Div -->
<div class="col-md-12 course_play_body" *ngIf="currentSegment.segment_type === 'Quiz'">
<div class="col-md-12 course_play_body text-center" *ngIf="showChildren === false">
<h1>{{currentSegment.name}}</h1>
<p class="text-left"> Now that you've finished this unit, It's time to take a short quiz and see what you learned so far!
You'll need to choose one out of four answers which you think is correct.
After you've finished the quiz, you'll get your grade. feel free to re-take this quiz as much as you like.
Good Luck!
</p>
<p class="text-left big-text"> {{currentSegment.questions.length}} questions </p>
<a (click) = "showChildren = true"><h4>Start Quiz</h4></a>
<button style="margin-top: 30%" type="button" class="prev btn btn-primary btn-lg" *ngIf="currentSegment.id > 1" [routerLink]="['/courses', course.id, 'segments', currentSegment.id-1]" (click)=getCurrentSegment(currentSegment.id-1)>Previous</button>
<button style="margin-top: 30%" type="button" class="next btn btn-primary btn-lg" *ngIf="currentSegment.id < course.segments?.length" [routerLink]="['/courses', course.id, 'segments', currentSegment.id+1]" (click)=getCurrentSegment(currentSegment.id+1)>Next</button>
</div>
<quiz-course *ngIf="showChildren === true" [items]="currentSegment?.questions"></quiz-course>
</div>
</div>
course-quiz.component.ts
import { Component, OnInit, Input } from '#angular/core';
import { ActivatedRoute, Router, Routes, NavigationEnd } from '#angular/router';
import { ICourse } from '../../course';
import { ISegment } from '../../course';
import { IQuestion } from '../../course';
import { CourseService } from '../../course.service';
import { PagerService } from '../../pager.service';
import * as _ from 'underscore';
#Component({
selector: 'quiz-course',
templateUrl: './course-quiz.component.html',
styleUrls: ['./course-quiz.component.sass']
})
export class CourseQuizComponent implements OnInit {
// the data I get from course-play component
#Input() items: IQuestion[];
// variables for pagination
pagedItems: IQuestion[];
pager: any = {};
public userAnswers = ['0', '0', '0', '0']; // array of user answers
public index: int; // index of userAnswers
public checkedAnswer: int; // the checked answer the user choose
public correctAnswer: boolean = false; // true if his answer correct, else false
public sum: int; // sum of the questions answered. needed to check if user finished quiz
constructor(private courseService: CourseService,
private route: ActivatedRoute,
private router: Router,
private pagerService: PagerService) { }
ngOnInit() {
this.setPage(1);
this.checkedAnswer = 0;
this.index = 0;
this.sum = 0;
}
// change pages
setPage(page: number) {
if (page < 1 || page > this.pager.totalPages) {
return;
}
// get pager object from service
this.pager = this.pagerService.getPager(this.items.length, page);
// get current page of items
this.pagedItems = this.items.slice(this.pager.startIndex, this.pager.endIndex + 1);
}
isChecked(value){
this.checkedAnswer = value;
}
// get value of the checked answer, check if it's correct and save it to the answer array
submitAnswer(correct) {
// if the user answer all the questions, go to finished page
if (sum == this.items.length) {
}
if (this.checkedAnswer == 0) {
// do something to notify user that an answer need to be checked
}
else if (this.checkedAnswer == correct) {
this.correctAnswer = true;
this.userAnswers[this.index] = "correct";
}
else {
this.correctAnswer = false;
this.userAnswers[this.index] = "incorrect";
}
this.index = this.index + 1;
this.sum = this.sum + 1;
}
}
course-quiz.component.html
<div class="container" *ngIf="sum < 4">
<div class="text-left quiz-body" *ngFor="let item of pagedItems">
<form>
<!-- items being paged -->
<h3>Question {{item.id}}/{{items.length}}</h3>
<h6>Question {{item.question}}</h6>
<ul class="items">
<li><input type="radio" id="answer1" name="answer" value="1" (click)="isChecked(1)"><label for="answer1">1. {{item.answer1}}</label></li>
<li><input type="radio" id="answer1" name="answer" value="2" (click)="isChecked(2)"><label for="answer2">2. {{item.answer2}}</label></li>
<li><input type="radio" id="answer1" name="answer" value="3" (click)="isChecked(3)"><label for="answer3">3. {{item.answer3}}</label></li>
<li><input type="radio" id="answer1" name="answer" value="4" (click)="isChecked(4)"><label for="answer4">4. {{item.answer4}}</label></li>
</ul>
<button type="submit" class="btn btn-primary mb-2" (click)="submitAnswer(item.correct)">Submit</button>
<!-- Submit Buttom -->
<!-- pager -->
<ul *ngIf="pager.pages && pager.pages.length" class="pagination">
<li class="page-item" [ngClass]="{disabled:pager.currentPage === 1}">
<a class="page-link" (click)="setPage(1)">First</a>
</li>
<li class="page-item" [ngClass]="{disabled:pager.currentPage === 1}">
<a class="page-link" (click)="setPage(pager.currentPage - 1)">Previous</a>
</li>
<li class="page-item" *ngFor="let page of pager.pages" [ngClass]="{active:pager.currentPage === page}">
<a class="page-link" (click)="setPage(page)">{{page}}</a>
</li>
<li class="page-item" [ngClass]="{disabled:pager.currentPage === pager.totalPages}">
<a class="page-link" (click)="setPage(pager.currentPage + 1)">Next</a>
</li>
<li class="page-item" [ngClass]="{disabled:pager.currentPage === pager.totalPages}">
<a class="page-link" (click)="setPage(pager.totalPages)">Last</a>
</li>
</ul>
</form>
</div>
</div>
<!-- If the user finished the quiz, this div will displaying instead -->
<div class="container" *ngIf="sum == 4">
<h3> You have just finished the quiz! </h3>
</div>
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
I have two inputs, each populated by their own ng-bootstrap datepicker that pops up when I click a button. The input populates properly when you choose a new date, but my problem is if I want to initialize the inputs to null or simply another value. I know my getDate function gets in the way of that, I'm just not sure how to change it to allow that.
Screenshot (datepicker opens with click of the button to the right of each input, one per input):
HTML:
<form class="form-inline">
<div>
<div class="form-group" [ngClass]="{'has-error':!secondForm.controls['startDate'].valid && secondForm.controls['startDate'].touched}">
<label>Start Date:</label>
<input style="width:250px" [value]="getDate('start')" class="form-control" type="text" [formControl]="secondForm.controls['startDate']">
</div>
<div style="display:inline-block">
<ngb-datepicker id="special" *ngIf="startCheck;" [(ngModel)]="startDate" (ngModelChange)="showDatePick(0)" [ngModelOptions]="{standalone: true}"></ngb-datepicker>
</div>
<button type="button" class="btn icon-calendar" (click)="showDatePick(0)"></button>
<div class="form-group" [ngClass]="{'has-error':!secondForm.controls['endDate'].valid && secondForm.controls['endDate'].touched}">
<label>End Date:</label>
<input style="width:250px" [value]="getDate('end')" class="form-control" type="text" [formControl]="secondForm.controls['endDate']">
</div>
<div style="display:inline-block">
<ngb-datepicker id="special" *ngIf="endCheck;" [(ngModel)]="endDate" (ngModelChange)="showDatePick(1)" [ngModelOptions]="{standalone: true}"></ngb-datepicker>
</div>
<button type="button" class="btn icon-calendar" (click)="showDatePick(1)"></button>
<button type="submit" class="btn icon-search" [disabled]="!secondForm.valid"></button>
<span [hidden]="!secondForm.hasError('endDateLessThanStartDate')" class="alert alert-danger first">End Date must be equal to or after Start Date</span>
</div>
</form>
Typescript:
import { Component } from '#angular/core';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import {NgbDateStruct} from '#ng-bootstrap/ng-bootstrap';
import {DatePipe} from "#angular/common";
#Component({
selector: 'calendar-pick',
styleUrls: ['../app.component.css'],
templateUrl: './calendarpick.component.html',
providers: [DatePipe]
})
export class CalendarPickComponent {
public dt: NgbDateStruct;
public dt2: NgbDateStruct;
public startCheck: boolean = false;
public endCheck: boolean = false;
secondForm : FormGroup;
public constructor(fb: FormBuilder, private datePipe: DatePipe) {
this.secondForm = fb.group({
'startDate' : [null, Validators.required],
'endDate' : [null, Validators.required]
}, {validator: this.endDateAfterOrEqualValidator})
}
public getDate(dateName: string) {
let workingDateName = dateName + 'Date';
let timestamp = this[workingDateName] != null ? new Date(this[workingDateName].year, this[workingDateName].month-1, this[workingDateName].day).getTime() : new Date().getTime();
this.secondForm.controls[dateName + 'Date'].setValue(this.datePipe.transform(timestamp, 'MM/dd/yyyy'));
}
public showDatePick(selector):void {
if(selector === 0) {
this.startCheck = !this.startCheck;
} else {
this.endCheck = !this.endCheck;
}
}
endDateAfterOrEqualValidator(formGroup): any {
var startDateTimestamp, endDateTimestamp;
for(var controlName in formGroup.controls) {
if (controlName.indexOf("startDate") !== -1) {
startDateTimestamp = Date.parse(formGroup.controls[controlName].value);
}
if (controlName.indexOf("endDate") !== -1) {
endDateTimestamp = Date.parse(formGroup.controls[controlName].value);
}
}
return (endDateTimestamp < startDateTimestamp) ? { endDateLessThanStartDate: true } : null;
}
}
Changed second line in getDate function to let timestamp = this[workingDateName] != null ? new Date(this[workingDateName].year, this[workingDateName].month - 1, this[workingDateName].day).getTime() : null;