i need to create a dynamic form . when click AddNew it create a new form .
i using this code in ts file :
addinfoForm:FormGroup;
infoNameList:FormArray;
infoModel:Productdetail;
constructor(private fb:FormBuilder,private router:Router,private tokenService:TokenstoreService) { }
ngOnInit() {
this.InfoForm();
}
/**
* AddInfoForm
*/
public InfoForm() {
this.addinfoForm=this.fb.group({
infoName:this.fb.array([this.CreateInfoName()])
})
this.infoNameList=this.addinfoForm.get('infoNames') as FormArray;
}
public CreateInfoName():FormGroup{
return this.fb.group({
infoName:['',Validators.compose([Validators.required])]
});
}
public AddInfoName(){
this.infoNameList.push(this.CreateInfoName());
}
public RemoveInfoName(index:number){
this.infoNameList.removeAt(index);
}
and use this in html :
<div class="panel-content">
<form class="form-line" [formGroup]="addinfoForm" (ngSubmit)="AddRole()">
<div formArrayName="infoNameList">
<div class="description" *ngFor="let name of addinfoForm.controls; let NameIndex=index" [formGroupName]="NameIndex">
<div [formGroupName]="i" class="row">
<label class="form-line"> Name: </label>
<input style="margin-right: 50px;" class="form-line" pInputText id="pFaName" formControlName="Name">
<app-filederrors [form]="addinfoForm"
field="pFaName"
nicename="Name">
</app-filederrors>
</div>
</div>
</div>
</form>
<button (click)="AddInfoName()">Add New </button>
<div class="button">
<button pButton type="button" label="REgister" (click)="AddCat()" [disabled]="!addinfoForm.valid" class="ui-button-rounded"></button>
<button pButton type="button" label="Cencel" class="ui-button-rounded ui-button-danger"></button>
</div>
</div>
but when i click it how me this Error :
AddinfoComponent.html:21 ERROR TypeError: Cannot read property 'push' of null
at AddinfoComponent.push../src/app/admin/admin/dashboard/productinfo/addinfo/addinfo.component.ts.AddinfoComponent.AddInfoName (addinfo.component.ts:41)
How Can i solve this problem ?
Maybe get('infoName') instead of get('infoNames')?
public InfoForm() {
this.addinfoForm=this.fb.group({
infoName:this.fb.array([this.CreateInfoName()])
})
this.infoNameList=this.addinfoForm.get('infoName') as FormArray;
}
The problem is that your infoNameList property has been declared, but not initialised so it's null.
Just do the following:
ngOnInit() {
this.inforNameList = this.fb.array([])
this.InfoForm();
}
wrong name should be infoName
public InfoForm() {
this.addinfoForm=this.fb.group({
infoName:this.fb.array([this.CreateInfoName()])
})
this.infoNameList=this.addinfoForm.get('infoName') as FormArray; // <- previously infoNames
}
Related
I am learning Angular, I created in my page 3 fields: Article, Quantity, Limit.
If each field is empty, a modal appears !
For now, I have no problem, the modal appears like I want.
Now, I have two questions please:
1- If my fields are completed, and that the user clicks on OK, my modal appears ?? How to I change this ?
2- You think that it is possible for example that the field quantity to be completed obligatorily. For example, if the field quantity is completed and no the fields article and limit. There is no modal to display.
I am stuck on these two questions...
component.html
<br><br>
<label for='quantity'>Article</label>
<div class="input-group">
<input type="text" class="form-control" id="article" name="article">
</div>
<label for='quantity'>Quantity</label>
<div class="input-group">
<input type="number" class="form-control" id="quantity" name="quantity">
</div>
<label for='quantity'>Limit</label>
<div class="input-group">
<input type="number" class="form-control" id="orderLimit" name="orderLimit">
</div>
<br>
<div class="row">
<div class="col-12">
<button class="btn btn-lg btn-outline-primary" (click)="open(mymodal)"> ok </button>
</div>
</div>
<!-- Modal -->
<ng-template #mymodal let-modal>
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">Error message</h4>
<button type="button" class="close" aria-label="Close button" aria-describedby="modal-title" (click)="modal.dismiss('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="table-responsive">
<table class="table table-striped">
<thead>
</thead>
<tbody>
<td>Fields cannot be empty</td>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" (click)="modal.close('Save click')">Ok</button>
</div>
</ng-template>
component.ts
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'appBootstrap';
closeResult: string | undefined;
constructor(private modalService: NgbModal) {}
open(content: any) {
this.modalService
.open(content, { ariaLabelledBy: 'modal-basic-title' })
.result.then(
result => {
this.closeResult = `Closed with: ${result}`;
},
reason => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
}
);
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
}
}
}
I can put my code below if you want:
https://stackblitz.com/edit/angular-ivy-kr7cyd?file=src/app/app.component.ts
1- If my fields are completed, and that the user clicks on OK, my
modal appears ?? How to I change this ?
No, It should not appear as you mentioned the modal should only open when the fields are empty.
2- You think that it is possible for example that the field quantity
to be completed obligatorily. For example, if the field quantity is
completed and no the fields article and limit. There is no modal to
display.
You can do that if you want. You can use ngModel and bind each input property that can help you track their values as soon as the user updates it. When you open or close the modal you can do it in the component method instead closing modal using the view reference variable.
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'appBootstrap';
article = 0, quantity = 0, orderLimit = 0;
closeResult: string | undefined;
constructor(private modalService: NgbModal) {}
open(content: any) {
this.modalService
.open(content, { ariaLabelledBy: 'modal-basic-title' })
.result.then(
result => {
this.closeResult = `Closed with: ${result}`;
},
reason => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
}
);
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
}
}
// this should be checked everytime when user clicks 'Ok'
openModal() {
// check if the values are '0'
// make sure the values come in as Number
if (orderLimit && article && quantity) {
// show modal
} else {
// no need to show modal
}
}
}
<label for='quantity'>Article</label>
<div class="input-group">
<input type="text" class="form-control" id="article" name="article" [(ngModel)]="article">
</div>
<label for='quantity'>Quantity</label>
<div class="input-group">
<input type="number" class="form-control" id="quantity" [(ngModel)]="quantity" name="quantity">
</div>
<label for='quantity'>Limit</label>
<div class="input-group">
<input type="number" class="form-control" id="orderLimit" [(ngModel)]="orderLimit" name="orderLimit">
</div>
I am trying to implement dynamic input into my current Angular Project. I have a problem with dynamic input. Here is that browser console error.
setValues is a function of DynamicFormComponent.
ERROR TypeError: Cannot read property 'setValues' of undefined
at SafeSubscriber._next (job-department-edit.component.ts:154)
at SafeSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.__tryOrUnsub (Subscriber.js:196)
at SafeSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.next (Subscriber.js:134)
at Subscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._next (Subscriber.js:77)
at Subscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (Subscriber.js:54)
at MapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/map.js.MapSubscriber._next (map.js:41)
at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (Subscriber.js:54)
at FilterSubscriber.push../node_modules/rxjs/_esm5/internal/operators/filter.js.FilterSubscriber._next (filter.js:38)
at FilterSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (Subscriber.js:54)
at MergeMapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/mergeMap.js.MergeMapSubscriber.notifyNext (mergeMap.js:84)
Parent Component(ts):
#ViewChild(DynamicFormComponent)
private readonly formComponent: DynamicFormComponent;
constructor() {
...
}
ngOnInit(): void {
this.appComponent.setPageTitle(this.pageName);
const splittedUrl = this.router.url.split('/');
this.languageCode = splittedUrl[1];
this.translateService.use(this.languageCode);
this.jobDepartmentId = splittedUrl[6];
this.getBoxTitle();
this.getCurrencies();
this.getJobDepartments();
}
ngAfterViewInit(): void {
this.inputs = this.getInputs();
if (this.jobDepartmentId !== undefined) {
this.getJobDepartment(this.jobDepartmentId);
}
}
Parent Component(html):
<div class="row">
<div class="col-sm-12">
<dynamic-form *ngIf="currencies.length && jobDepartments.length" [boxTitle]="boxTitle" [inputs]="inputs" [languageCode]="languageCode" (onSubmit)="onSubmit($event)"></dynamic-form>
</div>
</div>
Child Component(ts)
#Input()
languageCode: string;
#Input()
inputs: InputBase<any>[] = [];
#Input()
boxTitle = '';
#Output()
onSubmit = new EventEmitter<DynamicFormComponent>();
#Output()
onFormInit = new EventEmitter<void>();
form: FormGroup;
Child Component(html)
<app-card cardTitle="{{ boxTitle }}">
<form role="form" class="form-horizontal" [formGroup]="form" (ngSubmit)="submit()" autocomplete="off" novalidate>
<div class="row">
<ng-container *ngFor="let input of inputs">
<div class="col-md-6">
<dynamic-input [input]="input" [form]="form" [languageCode]="languageCode"></dynamic-input>
</div>
</ng-container>
</div>
<ng-content></ng-content>
<div class="form-group text-center">
<button type="submit" name="submit" id="submit" class="btn btn-success">
{{'save' | translate}}<span style="margin-left:10px;"><i class="feather icon-save"></i></span>
</button>
</div>
</form>
</app-card>
Child component is not fully loaded when the parent component did.
How can I solve this problem?
I am working on a small project that allows the user to add dynamic fields. On clicking a button I have been able to create input fields dynamically. I am trying to access the value of each field and push it to the service. On the other hand, another component should make the number of div depending upon the number of input fields created by the user and each div should contain a title depending upon the user input in the input field.
register.component.html
<h2>Demo App</h2>
<form [formGroup]="myForm">
<button (click)="addRooms()">Add Room </button>
<div formArrayName="addRoom">
<div *ngFor="let r of Rooms.controls; let i=index" [formGroupName]="i">
<mat-form-field>
<input matInput placeholder="Enter A Room Name" formControlName="roomName" (keyup)="abc()"/>
</mat-form-field>
<button (click)="deleteRoom(i)">Delete</button>
</div>
</div>
<input type="button" (click)="getRoomValues()" value="Get">
</form>
register.component.ts
import { Component, OnInit} from '#angular/core';
import { RegisterModel } from '../models/register.model';
import {FormGroup, FormBuilder, Validators, FormArray, FormControl } from '#angular/forms';
import { Router } from '#angular/router';
#Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
export class RegisterComponent implements OnInit, AfterViewInit {
myForm: FormGroup;
room: FormGroup;
constructor(private formBuilder:FormBuilder, private r:Router, private _ele:ElementRef, private _render: Renderer) { }
ngOnInit() {
this.myForm = this.formBuilder.group({
addRoom: this.formBuilder.array([]),
tst: new FormControl()
});
}
getVal(){
console.log(this.myForm.value.tst);
}
get Rooms(){
return this.myForm.get('addRoom') as FormArray;
}
addRooms(){
this.room = this.formBuilder.group({
roomName:new FormControl()
})
this.Rooms.push(this.room);
console.log(this.Rooms);
}
abc(){
console.log(this.room.value.roomName);
}
deleteRoom(i){
this.Rooms.removeAt(i);
}
get roomNames(){
return this.myForm.get('roomNames') as FormArray;
}
getRoomValues(){
console.log(this.myForm.value.addRoom)
}
Below is the code sample for couple of fields with dynamic Add & Remove functionality.
<form [formGroup]="linksForm" class="form-horizontal">
<div class="form-group">
<label class="col-md-2 control-label">Links
</label>
<div class="col-md-10">
<div formArrayName="links" *ngFor="let item of linksForm.get('links').controls; let i = index;">
<div [formGroupName]="i">
<div class="col-md-5">
<input class="form-control col-md-5" formControlName="name" placeholder="Name">
</div>
<div class="col-md-5">
<input type="url" pattern="https?://.+" placeholder="http://example.com" class="form-control col-md-5"
formControlName="link">
</div>
<div class="col-md-2">
<button class="btn btn-warning btn-xs m-t-sm" type="button" (click)="removeItem(i)">
<i name="save" class="fa fa-trash"></i>
</button>
<button *ngIf="i == linksForm.get('links').controls.length - 1" class="btn btn-primary btn-xs m-t-sm" type="button" (click)="addItem()">
<i name="save" class="fa fa-plus"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</form>
TS : Declare below variables in class :
linksForm: FormGroup;
links: FormArray;
Inside ngOnInit() initialize form with at least one row :
this.linksForm = new FormGroup({
'links': new FormArray([this.createItem()])
});
Add below functions for Add / Remove :
addItem(): void {
this.links = this.linksForm.get('links') as FormArray;
this.links.push(this.createItem());
}
removeItem(index: any) {
this.links.removeAt(index);
if (this.links.length == 0) {
this.addItem();
}
}
createItem(): FormGroup {
return new FormGroup({
id: new FormControl(),
name: new FormControl(),
link: new FormControl(),
createdAt: new FormControl()
});
}
I have three components: GalleryAddComponent to add a new element, GalleryItemComponent, to edit an element, FieldsComponent, the form I want to use in the components: GalleryAddComponent and GalleryItemComponent. All components are inside the GalleryComponent. But when I go to the component GalleryAddComponent to add a new element I get the error: ERROR TypeError: Cannot read property 'controls' of undefined. Also in the component: GalleryItemComponent.
Help solve this problem so that the editing and adding logic works correctly.
template of GalleryAddComponent
<div class="card">
<div class="card-body">
<form [formGroup]="angForm" novalidate>
<app-fields [formGroup]="angForm"></app-fields>
<div class="form-group but-group">
<button (click)="addPost(title.value, url.value); angForm.reset(title.value, url.value)"
[disabled]="angForm.pristine || angForm.invalid"
class="btn btn-primary">Add
</button>
<a routerLink="/" class="btn btn-danger">Back</a>
</div>
</form>
</div>
</div>
code of GalleryAddComponent
export class GalleryAddComponent implements OnInit {
angForm: FormGroup;
isAdded: boolean = false;
constructor(private fb: FormBuilder, private galleryService: GalleryService) {}
ngOnInit() {
this.angForm = this.fb.group({
title: ['', Validators.required],
url: ['', Validators.required]
});
}
addPost(title: string, url: string): void {
this.galleryService.add(title, url).subscribe(res => {
this.isAdded = true;
});
}
}
template of GalleryItemComponent
<div class="card" *ngIf="toggleEdit">
<h4>Edit your post</h4>
<div class="card-body">
<form [formGroup]="angForm" novalidate>
<app-fields [formGroup]="angForm"></app-fields>
<div class="form-group but-group">
<input type="button"
(click)="updatePost(title.value, url.value)"
[disabled]=" angForm.invalid"
class="btn btn-primary" value="Update Post">
</div>
</form>
</div>
</div>
code of GalleryItemComponent
export class GalleryItemComponent implements OnInit {
pic: Picture;
angForm: FormGroup;
constructor(private route: ActivatedRoute,
private galleryService: GalleryService, private fb: FormBuilder) {}
ngOnInit() {
this.angForm = this.fb.group({
title: ['', Validators.required],
url: ['', Validators.required]
});
this.showPost();
}
showPost(): void {
this.route.params.subscribe(params => {
this.galleryService.getPicture(params['id']).subscribe(res => {
this.pic = res;
this.angForm.setValue({title: res.title, url: res.url})
})
})
}
updatePost(title: string, url: string): void {
this.route.params.subscribe(params => {
this.galleryService.update(title, url, params['id']).subscribe(res => {
if (res.id === this.pic.id) {
this.pic.title = title;
this.pic.url = url;
}
});
});
}
}
template of FieldsComponent
<div [formGroup]="formGroup">
<div class="form-group">
<label class="col-md-4">Picture Title</label>
<input type="text" class="form-control" formControlName="title" minlength="1" #title/>
</div>
<div *ngIf="angForm.controls['title'].invalid && (angForm.controls['title'].dirty || angForm.controls['title'].touched)"
class="alert alert-danger">
<div *ngIf="angForm.controls['title'].errors.required">
Title is required.
</div>
</div>
<div class="form-group">
<label class="col-md-4">Picture Address (url)</label>
<input type="url" class="form-control" formControlName="url" #url pattern="https?://.+"
title="Include http://"/>
</div>
<div *ngIf="angForm.controls['url'].invalid && (angForm.controls['url'].dirty || angForm.controls['url'].touched)"
class="alert alert-danger">
Address(url) is required.
<div *ngIf="angForm.controls['url'].errors.required ">
</div>
</div>
</div>
code of FieldsComponent
export class FieldsComponent implements OnInit {
#Input() formGroup: FormGroup;
constructor() {}
ngOnInit() {}
}
You are getting this error because you are referencing angForms.controls in your FieldsComponent which only has one variable: formGroup.
Simply replace angForms in the template HTML code with formGroup and the issue should be resolved.
<div [formGroup]="formGroup">
<div class="form-group">
<label class="col-md-4">Picture Title</label>
<input type="text" class="form-control" formControlName="title" minlength="1" #title />
</div>
<div *ngIf="formGroup.controls['title'].invalid && (formGroup.controls['title'].dirty || formGroup.controls['title'].touched)"
class="alert alert-danger">
<div *ngIf="formGroup.controls['title'].errors.required">
Title is required.
</div>
</div>
<div class="form-group">
<label class="col-md-4">Picture Address (url)</label>
<input type="url" class="form-control" formControlName="url" #url pattern="https?://.+" title="Include http://" />
</div>
<div *ngIf="formGroup.controls['url'].invalid && (formGroup.controls['url'].dirty || formGroup.controls['url'].touched)"
class="alert alert-danger">
Address(url) is required.
<div *ngIf="formGroup.controls['url'].errors.required ">
</div>
</div>
</div>
I think the easy solution here, is to set up the formgroup in the parent component, and pass it on to the child components as input, hence it is given to the child component before the child component html is loaded.
The other solution I would recommend is to use the Resolve Technique offered by Angular, this allows you to load all data before a component is loaded with a quite straight-forward setup.
Here are some references:
https://www.techiediaries.com/angular-router-resolve/
https://alligator.io/angular/route-resolvers/
Cannot read property 'controls' of undefined. This error can come only coz instead of formGroup you have used angForm. Try replacing it as shared by #Wrokar. Also what I understand is you want to show consolidated error messages. So instead of doing it in html for each formcontrol, you should do it in component.ts and make it more generic, by subscribing to value change of each control like below, and show the consolidated error message.
for (const field in this.formGroup.controls) { // 'field' is a string
const control = this.form.get(field); // 'control' is a FormControl
control.valueChanges.subscribe(
(res) => {
// check if it is pristine, dirty, invalid, touched, pattern match if
any
// you can access control.errors
// create the consolidated list and append it in the list of error
messages
}
)
}
Its better to make it generic coz, tomorrow your form can have additional fields and then you dont have to change you FieldsComponent.
I learn Angular from Tour of heroes and I develop my own project.
I have a backend written in Java witch returns status 500 if operation is failed.
The response if operation is failed looks like below:
{"timestamp":1497911402842,"status":500,"error":"Internal Server Error","exception":"javax.validation.ConstraintViolationException","message":"Content of message"}
My service:
getProducts(): Promise<Product[]> {
return this.http.get(this.productsUrl)
.toPromise()
.then(response => response.json() as Product[])
.catch(this.handleError);
}
private handleError(error: any): Promise<any> {
console.error('An error occured ', error);
return Promise.reject(error.message);
}
My form component:
export class ProductFormComponent {
constructor(private productService: ProductService) {}
model: Product = new Product(
1,
'Name of product'
);
result: Product= new Product(
null,
null
);
submitted = false;
onSubmit(): void {
this.submitted = true;
this.productService.create(this.model)
.then(response => this.result = response);
}
clearForm(): void {
this.model = new Product(
null,
''
);
}
}
And form looks like below:
<div class="container">
<div [hidden]="submitted" class="container">
<h1>Product form</h1>
<form (ngSubmit)="onSubmit()" #productForm="ngForm">
<div class="form-group">
<label for="productId">Product id:</label>
<input type="text" class="form-control" id="productId" required [(ngModel)]="model.productId" name="productId" #productId="ngModel">
</div>
<div class="form-group">
<label for="productName">Product name:</label>
<input type="text" class="form-control" id="productName" required [(ngModel)]="model.productName" name="productName" #productName="ngModel">
<div [hidden]="productName.valid" class="alert alert-danger">
Product name is required
</div>
</div>
<button type="button" class="btn btn-default" (click)="clearForm(); productForm.reset()">Clear form</button>
<button type="submit" class="btn btn-success" [disabled]="!productForm.form.valid">Submit</button>
</form>
</div>
<div [hidden]="!submitted">
<h2>Submitted product:</h2>
<div class="row">
<div class="col-xs-3">Product name:</div>
<div class="col-xs-9 pull left">{{ result.productName }}</div>
</div>
<div class="row">
<div class="col-xs-3">Product id:</div>
<div class="col-xs-9 pull left">{{ result.productId }}</div>
</div>
<br/>
<button class="btn btn-primary" (click)="submitted=false">Edit</button>
</div>
</div>
And the problem is that when I submit incorrect value I get status 500 in response and I would like to print result as null values or print some message if result is null but in result I get values from form. And I don't know how to catch the bad status from response. Then I could set adequate statement or message.
Make the following adjustment in your handleError function:
return Promise.reject(error);
Then add following code in onSubmit() function:
onSubmit(): void {
this.submitted = true;
this.productService.create(this.model)
.then(response => { this.result = response },
error => {
if(error.status == 500){
alert(JSON.stringify(error));
this.result = null;
//you can add more steps here
}
});
}