how to works with forms in array with angular 2 - html

I've been working with reactive form like I show in the link
https://plnkr.co/edit/ApCn3YicMjfm2vhSOudj?p=preview
this is my form
<div *ngFor="let item of data; let index = index">
<form novalidate (ngSubmit)="onSubmit(user)" [formGroup]="user">
<label>
<span>Full name</span>
<input type="text" placeholder="Name" formControlName="name">
</label>
<div class="error" *ngIf="user.get('name').touched && user.get('name').hasError('required')">
Name is required
</div>
<div class="error" *ngIf="user.get('name').touched && user.get('name').hasError('minlength')">
Minimum of 2 characters
</div>
<div formGroupName="account">
<label>
<span>Email address</span>
<input type="email" placeholder="Email" formControlName="email">
</label>
<div class="error" *ngIf="user.get('account').get('email').hasError('required') && user.get('account').get('email').touched">
Email is required
</div>
<label>
<span>Confirm address</span>
<input type="email" placeholder="Address" formControlName="confirm">
</label>
<div class="error" *ngIf="user.get('account').get('confirm').hasError('required') && user.get('account').get('confirm').touched">
Confirming email is required
</div>
</div>
<button type="submit" [disabled]="user.invalid">Sign up</button>
</form>
</div>
but my problem is that I have an ngFor, every time I submit the form it push the data to array.
How can I do if I want for example submit my first array and push the data to position 0 of my data array, if I submit my second form, it will push the data to position 1
But my second form should be empty

maybe you wanna using form array like this:
#Component({
selector: 'signup-form',
template: `
<form novalidate (ngSubmit)="onSubmit()" [formGroup]="users">
<div formArrayName="data">
<ng-container *ngFor="let user of fData.controls; index as i">
<div [formGroupName]="i">
<label>
<span>Full name</span>
<input type="text" placeholder="Name" formControlName="name">
</label>
<div class="error" *ngIf="user.get('name').touched && user.get('name').hasError('required')">
Name is required
</div>
<div class="error" *ngIf="user.get('name').touched && user.get('name').hasError('minlength')">
Minimum of 2 characters
</div>
<div formGroupName="account">
<label>
<span>Email address</span>
<input type="email" placeholder="Email" formControlName="email">
</label>
<div
class="error"
*ngIf="user.get('account').get('email').hasError('required') && user.get('account').get('email').touched">
Email is required
</div>
<label>
<span>Confirm address</span>
<input type="email" placeholder="Address" formControlName="confirm">
</label>
<div
class="error"
*ngIf="user.get('account').get('confirm').hasError('required') && user.get('account').get('confirm').touched">
Confirming email is required
</div>
</div>
<button type="submit" [disabled]="user.invalid">Sign up</button>
</div>
</ng-container>
</div>
</form>
`
})
export class SignupFormComponent implements OnInit {
user: FormGroup;
users: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.user = this.buildGroup();
this.users = this.fb.group({
data: this.fb.array([this.user])
});
}
get fData() {
return this.users.get('data') as FormArray;
}
buildGroup() {
return this.fb.group({
name: ['', [Validators.required, Validators.minLength(2)]],
account: this.fb.group({
email: ['', Validators.required],
confirm: ['', Validators.required]
})
});
}
onSubmit() {
this.fData.push(this.buildGroup());
const {valid, value} = this.fData;
console.log(valid, value);
}
}
basically, we're using FormArray to handle array of data, then loop through it.
in template, each time you loop through array item, Angular will store current AbstractControl in index variable (in above is i).
you can see form in action here: https://plnkr.co/edit/E5Qzm85LksSCZAloXZz5?p=preview
The API document here: https://angular.io/docs/ts/latest/api/forms/index/FormArray-class.html
you can use at, removeAt, etc to access or delete at specific index.

Related

Angular Form Containing input values that should create a JSON object with other JSON objects as atributes

Im trying to create a form that will return as its value an JSON object that contains as an attribute a list of objects. Form value JSON example :
{
"name" : "contestName",
"teams" :[
{
"name":"team1",
"wins":1,
"loses":1},
{
"name":"team2",
"wins":2,
"loses":2}
]
}
My Form Looks like :
<form id="form" #ContestForm="ngForm" (ngSubmit)="addNewContest(ContestForm.value)">
<div class="form-group" [(ngModel)]="contest" >
<input type="Contest Name" [(ngModel)]="name" class="form-control" placeholder="Contest Name">
<br>
<div class="form-group" [(ngModel)]="teams" *ngFor="let i of [].constructor(2)">
<br>
<label>Team {{i}} :</label>
<br><br>
<input type="Name" [(ngModel)]="name" class="form-control" placeholder="Name">
<br>
<div class="form-group">
<input type="Wins" [(ngModel)]="wins" class="form-control" placeholder="Wins">
</div>
<br>
<div class="form-group">
<input type="Loses" [(ngModel)]="loses" class="form-control" placeholder="Loses">
</div>
</div>
<br>
</div>
</form>
I tried this and setting the values through typescript in a createObjectFromForm function that would individually assign the values and return the object but got no results, I am hoping someone faced this issue as I think it`s a pretty common one.
Thanks Brandon Taylor, I used a reactive form and form groups and reached the desired result! example :
form(in html file) :
<form [formGroup]="contestForm" (ngSubmit)="alert()">
<input formControlName="name"/>
<div formArrayName="teams" *ngFor="let team of getTeamsControls(); index as i">
<div formGroupName="{{i}}">
<label> name </label>
<input type="text" id="name" name="name" formControlName="name">
<label> wins </label>
<input type="text" id="wins" name="wins" formControlName="wins">
<label> loses </label>
<input type="text" id="loses" name="loses" formControlName="loses">
</div>
</div>
<p>
<button type="submit">Submit</button>
</p>
</form>
form declaration and getTeamsControls(); function (in typescript file):
contestForm = new FormGroup({
name: new FormControl(''),
teams: new FormArray([
new FormGroup({
name : new FormControl(''),
wins : new FormControl(''),
loses : new FormControl('')
})
,
new FormGroup({
name : new FormControl(''),
wins : new FormControl(''),
loses : new FormControl('')
})
])
})
getTeamsControls() { return (<FormArray>this.contestForm.get('teams')).controls;}

How can I get the value to insert into a form for edit to update the record?

First I have a table to show all the record of books, I have a button is for going to edit the record, and I want to edit on the editbook.html and when I click submit , the record will be updated. May I know how can I implement this?
<tbody>
<tr *ngFor="let book of books">
<td class="td-title">{{book.title}}</td>
<td>{{book.author}}</td>
<td>{{book.price}}</td>
<td>{{book.isbn}}</td>
<td>{{book.details.pages}}</td>
<td>{{book.details.language}}</td>
<td class="td-desc">{{book.description}}</td>
<td><button type="button"><a [routerLink]="['/editbook/', book.isbn]">Edit</a></button></td>
<td><button type="button">Delete</button></td>
</tr>
</tbody>
And now I going to edit the record when I click the edit button.
and my edit form html
editbook.html
<div *ngIf="book" class="bookdetail-block">
<div *ngFor="let bookdetail of book" class="bookdetail">
<h1>Edit Book</h1>
<form [formGroup]="editbookForm" (ngSubmit)="onSubmit()">
<label>Title: <input type="text" formControlName="title">
<div *ngIf="submitted && editbookForm.controls.title.errors" class="error">
<div *ngIf="editbookForm.controls.title.errors.required">Required</div>
</div>
</label>
<label>Author: <input type="text" formControlName="author">
<div *ngIf="submitted && editbookForm.controls.author.errors" class="error">
<div *ngIf="editbookForm.controls.author.errors.required">Required</div>
</div>
</label>
<label>Description: <textarea type="text" formControlName="description"></textarea>
<div *ngIf="submitted && editbookForm.controls.description.errors" class="error">
<div *ngIf="editbookForm.controls.description.errors.required">Required</div>
</div>
</label>
<label>Page: <input type="number" min="1" max="10000" formControlName="page">
<div *ngIf="submitted && editbookForm.controls.page.errors" class="error">
<div *ngIf="editbookForm.controls.page.errors.required">Required</div>
</div>
</label>
<label>language:
<select formControlName="language">
<option value="English">English</option>
<option value="Traditional Chinese">Traditional Chinese</option>
<option value="Simpify Chinese">Simpify Chinese</option>
</select>
<div *ngIf="submitted && editbookForm.controls.page.errors" class="error">
<div *ngIf="editbookForm.controls.page.errors.required">Please select one option</div>
</div>
</label>
<br />
<label>Price: <input type="number" formControlName="price">
<div *ngIf="submitted && editbookForm.controls.price.errors" class="error">
<div *ngIf="editbookForm.controls.price.errors.required">Required</div>
</div>
</label>
<label>ISBN: <input type="text" formControlName="isbn">
<div *ngIf="submitted && editbookForm.controls.isbn.errors" class="error">
<div *ngIf="editbookForm.controls.isbn.errors.required">Your name is required</div>
</div>
</label>
<label>Image: <input (change)="onFileSelect($event)" type="file">
<div *ngIf="imageData" class="error">
<img [src]="imageData" [alt]="editbookForm.value.name">
</div>
</label>
<label style="display:none">ImageData:
<!-- <div *ngIf="submitted && editbookForm.controls.image.errors" class="error"> -->
<input type="hidden" id="uploadImage">
<!-- </div> -->
</label>
<input type="submit" value="Update Book" class="cta">
</form>
<div *ngIf="success" class="results">
<p>The Book is updated into the record.</p>
</div>
</div>
</div>
and the editbook.ts
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '#angular/forms';
import { DataService } from '../data.service';
import { ActivatedRoute, Router } from "#angular/router";
interface HTMLInputEvent extends Event {
target: HTMLInputElement & EventTarget;
}
#Component({
selector: 'app-editbook',
templateUrl: './editbook.component.html',
styleUrls: ['./editbook.component.scss']
})
export class EditbookComponent implements OnInit {
isbn: any;
book: any;
editbookForm: FormGroup;
submitted = false;
success = false;
imageData : String;
constructor(private formBuilder: FormBuilder, private data: DataService, private router: Router, private route: ActivatedRoute) { }
ngOnInit() {
this.isbn = this.route.snapshot.paramMap.get("isbn");
this.data.getOneBook(this.isbn).subscribe(data =>{
console.log({ data }); //show data
this.book = data
//console.log(this.books);
})
}
onFileSelect(event?: HTMLInputEvent){
console.log("selected file")
console.log(event)
let files: any = event.target.files[0];
console.log(files);
const reader = new FileReader();
reader.onloadend = () => {
var b64 = reader.result.toString();
let el :any = document.querySelector('#uploadImage');
el.value = b64;
}
reader.readAsDataURL(event.target.files[0]);
}
onSubmit() {
/*
// upload image
this.bookForm.reset();
this.imageData = null;
*/
this.submitted = true;
if(this.editbookForm.invalid) {
return;
}
this.data.insertBook(this.editbookForm.controls);
this.success = true;
}
}
how can I insert the value into the html input ?
Don't use ngFor for insert the value into the html input. use angular patch method.
For example:
this.bookForm.patchValue({
title: this.book.title,
author: this.book.author
});
You can also initialize value on form:
this.bookForm({
title:[this.book.title],
author:[this.book.author]
});
Suppose you have two fields for the moment (title and author), you complete the other fields later after you be sure everything is fine.
Start with a simple example(Remove the validations from HTML for now).
So, initially, your HTML should be something like this:
<form [formGroup]="editbookForm" (ngSubmit)="onSubmit()">
<label>Title:
<input type="text" formControlName="title">
</label>
<label>Author:
<input type="text" formControlName="author">
</label>
<input type="submit" value="Update Book" class="cta">
</form>
Then your OnInit should be something like this:
ngOnInit() {
this.isbn = this.route.snapshot.paramMap.get("isbn");
if (this.isbn) {
//Cleaning the form from the previous data
if (this.editbookForm) this.editbookForm.reset();
this.data.getOneBook(this.isbn)
.pipe(take(1)) //To ensure that it will unsubscribe later
.subscribe((data) => {
this.book = data;
//Filling the form
this.editbookForm.patchValue({
title: this.book.title,
author: this.book.author
});
});
}
}
If that works, step by step add validations and other things as you like.
Examples are:
<div *ngIf="submitted && editbookForm.controls.author.errors" class="error">
<div *ngIf="editbookForm.controls.author.errors.required">Required</div>
</div>
and
<div *ngIf="book" class="bookdetail-block">
<div *ngFor="let bookdetail of book" class="bookdetail">
<h1>Edit Book</h1>
and so on.

HTML Form field not showing touched status - Angular 7

I am trying to input some data in a form, but it is not giving me the touched status. Therefore, an error will always occur when sending a message back to the user.
I am using FormBuilder in my TS file to store the values from the HTML. It gives me this error regardless if I put in data or not.
I am lost.
The error
firstName: FormControl {validator: ƒ, asyncValidator: ƒ, _onCollectionChange:
ƒ, pristine: true, touched: false, …}
Value:
value: {firstName: "", .... }
I have tried to check for pristine in the ngIf condtion, but it doesn't.
Here is my HTML code:
<form [formGroup]="formInfo" (ngSubmit)="validateForm()">
<label>First Name <input type="text" maxlength="35" />
<div *ngIf="submitted && formInfo.controls.firstName.errors" class="error">
<div *ngIf="(formInfo.controls.firstName.pristine) && (formInfo.controls.firstName.errors.required)">Your first
name is required.</div>
</div>
</label>
....
</form>
And here's my TypeScript code:
// Class Attributes
formInfo: FormGroup;
submitted = false;
success = false;
constructor(private builder: FormBuilder) { }
// Form data as an object
ngOnInit() {
this.formInfo = this.builder.group({
firstName: ['', Validators.required],
....
});
}
// Validates the form
validateForm() {
this.submitted = true;
console.log(this);
console.log(this.formInfo);
if (this.formInfo.invalid) {
return;
}
this.success = true;
}
}
I just want the form to say, you need to type in a value if the user has not. Otherwise, there will be no error message.
I have added the following code to see if there is even value in my TS file.
<form [formGroup]="formInfo" (ngSubmit)="validateForm()">
<label>First Name <input type="text" maxlength="35" />
<div *ngIf="submitted && formInfo.controls.firstName.errors" class="error">
<div *ngIf="(formInfo.controls.firstName.pristine) && (formInfo.controls.firstName.errors.required)">Your first
name is required.</div>
</div>
</label>
....
</form>
<!-- I added this -->
<div *ngIf="submitted">
<strong>First Name</strong>
<span>{{ formInfo.controls.firstName.value }}</span>
</div>
It seems that the value is never saved into the TS.
formInfo.controls.firstName.pristine will be true if the user has not yet changed the value in the UI.
https://angular.io/api/forms/AbstractControl#pristine
You'll want to modify
<div *ngIf="(formInfo.controls.firstName.pristine) && (formInfo.controls.firstName.errors.required)">Your first
name is required.</div>
to be
<div *ngIf="formInfo.controls.firstName.invalid && formInfo.controls.firstName.errors.required">Your first
name is required.</div>
To Solve my question you just need to change
<form [formGroup]="formInfo" (ngSubmit)="validateForm()">
<label>First Name <input type="text" maxlength="35" />
<div *ngIf="submitted && formInfo.controls.firstName.errors" class="error">
<div *ngIf="(formInfo.controls.firstName.pristine) &&
(formInfo.controls.firstName.errors.required)">Your first
name is required.</div>
</div>
</label>
....
</form>
To
<form [formGroup]="formInfo" (ngSubmit)="validateForm()">
<label>First Name <input type="text" maxlength="35" formControlName="firstName">
<div *ngIf="submitted && formInfo.controls.firstName.errors" class="error">
<div *ngIf="(formInfo.controls.firstName.pristine) &&
(formInfo.controls.firstName.errors.required)">Your first
name is required.</div>
</div>
</label>
....
</form>
Sorry for the inconvenience...

ERROR TypeError: Cannot read property ' ' of undefined

I'm trying to make a form field to add new hotel and specify the address.
I have the hotel class:
export class Hotel {
public constructor (
public name:string,
public address: Address,
){}
}
export class Address {
country:string;
town :string;
street: string;
}
this's the function addHotel in the component:
addHotel(hotel:any, address:any){
if (this.form.valid) {
let newHotel = new Hotel(hotel["name"], (hotel.address["country"], hotel.address["town"], hotel.address["street"]));
the html form is very simple:
<div class="tab-pane" >
<div class="form-group">
<label class="text-gray">Name :</label>
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-envelope-o"></i></div>
<input formControlName="name" class="form-control" type="text">
</div>
</div>
</div>
<div class="tab-pane" id="address">
<div class="form-group">
<label class="text-gray">Country :</label>
<div class="input-group" >
<div class="input-group-addon"><i class="fa fa-map-marker"></i></div>
<input class="form-control" type="text">
</div>
<label class="text-gray">Town :</label>
<div class="input-group" >
<div class="input-group-addon"><i class="fa fa-map"></i></div>
<input class="form-control" type="text">
</div>
<label class="text-gray">Street :</label>
<div class="input-group" >
<div class="input-group-addon"><i class="fa fa-map-signs"></i></div>
<input class="form-control" type="text">
</div>
</div>
</div>
I even tried to add new function, addAddress:
addAddress(hotel,address):any{
//let newAddress = new Address(hotel.address["country"], ,hotel.address["town"], hotel.address["street"]);
this.address.push(hotel.address["country"], hotel.address["town"], hotel.address["street"]);
console.log(this.address);
return this.address;
}
but it always return empty array {country:null, town:null, street:null}
or the famous error: ERROR TypeError: Cannot read property 'country' of undefined
Use the safe navigation operator (?)
look like this
<p>Employer: {{address?.country}}</p>
The safe navigation operator (?) means that the employer field is
optional and if undefined, the rest of the expression should be
ignored.
Reference
https://angular.io/guide/cheatsheet
even when I add console.log(address.town); to the addAddress function, I get undefined. what I have to do??
Read this post it having all details you want : https://angular.io/guide/reactive-forms#nested-formgroups
Please define binding between template and typescript for getting value from template form control example :
export class etailComponent5 {
hotelForm: FormGroup;
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
this.hotelForm= this.fb.group({ // <-- the parent FormGroup
name: [''],
address: this.fb.group({ // <-- the child FormGroup
street: '',
town: '',
country: ''
})
});
}
saveform() {
const hotel:Hotel = hotelForm.value;
//or to get individual control you need to do like this
// const street= hotelForm.get('address.street').value
// const town= hotelForm.get('address.town').value
// const coutnry= hotelForm.get('address.country').value
}
}
<form [formGroup]="hotelForm" >
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div formGroupName="address" class="well well-lg">
<h4>Secret Lair</h4>
<div class="form-group">
<label class="center-block">Street:
<input class="form-control" formControlName="street">
</label>
</div>
<div class="form-group">
<label class="center-block">Town:
<input class="form-control" formControlName="town">
</label>
</div>
<div class="form-group">
<label class="center-block">Country:
<input class="form-control" formControlName="country">
</label>
</div>
</div>
</form>
Working :
const address = new Address();
address.country='abc';
address.street= 'abc';
address.town ='abc';
let newHotel = new Hotel('abc', address);
console.log(newHotel.name);
console.log(`${newHotel.address.country},${newHotel.address.street},${newHotel.address.town}`);
As per you structure address - property is class used in hotel, so you need to initialized it and then you can assign to address property. as given in above code..
In your code you are trying to set value without initializing it
I suggest make use of interface instead of class
export interface Hotel {
name:string;
address: Address;
}
export interface Address {
country:string;
town :string;
street: string;
}
const hotel: Hotel = { name: 'name of hotel',
address: { country:'abc', town : 'abc', city:'abc' }
};

Value comparison in <div> using *ngFor

I am trying to compare values coming from database to the value I am entering on my angular 2 model driven form. I want to display a div when the values are not equal. I am trying the logic below but I am unable to make it work. Help will be appreciated.
View
<form [formGroup]="reguserform" #f="ngForm" (ngSubmit)="register()">
<fieldset>
<div class="form-group">
<label for="Username">Username</label>
<input class="form-control"
[(ngModel)]="user.Username"
type="text" id="Username"
formControlName="Username" />
</div>
<div class="alert alert-danger"
*ngIf="reguserform.controls.Username.touched && reguserform.controls.Username.errors">
<div *ngIf="reguserform.controls.Username.errors.required"
class="alert alert-danger">
Please enter a valid Username...
</div>
<div *ngFor="let r of rusers">
<div *ngIf="r.Username == user.Username" class="alert alert-danger">Username is taken</div>
</div>
</div>
</fieldset>
Component
getUsers() {
this.authenticationService.getRegUser().subscribe(
res => this.rusers = res
);
}
Everything is fine from the db end the object is being logged in console too but at the time of comparison no div is shown.
Your idea works in general, but that *ngIf="reguserform.controls.Username.touched && reguserform.controls.Username.errors" isn't true!
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{name}}</h2>
<form [formGroup]="reguserform" #f="ngForm" (ngSubmit)="register()">
<fieldset>
<div class="form-group">
<label for="Username">Username</label>
<input class="form-control"
[(ngModel)]="user.Username"
type="text" id="Username"
formControlName="Username" />
</div>
<div class="alert alert-danger">
<div *ngFor="let r of rusers">
<div *ngIf="r.Username == user.Username" class="alert alert-danger">Username is taken</div>
</div>
</div>
</fieldset>
</form>
</div>
`,
})
export class App {
public reguserform: FormGroup; // our model driven form
user = {};
rusers = [
{ Username: 'mxii' },
{ Username: 'test' },
{ Username: 'peter' }
];
constructor(private _fb: FormBuilder) {
this.name = 'Angular2'
}
ngOnInit() {
this.reguserform = this._fb.group({
Username: ['', [<any>Validators.required, <any>Validators.minLength(1)]]
});
}
}
See my live-demo: https://plnkr.co/edit/SnHfoAL2dnuwKkrFYGzE?p=preview
When you write res => this.rusers = res I guess you put the http response in your variable. It's not an iterable object. Maybe you need to write this :
getUsers() {
this.rusers = this.authenticationService.getRegUser().map(
res => res.json()
);
}