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?
Related
Angular
In the first few tags I have an input tag input #title = "ngModel" [(ngModel)]="product.title" this code where I am trying to use two way binding to be able to edit my form and values that I get from the firebase database. I created an empty product object in product-form.component.ts but I get the property type error. I am not sure why the empty product object causing error because the tutorial I'm watching has the same approach. Goal is to be able to use that empty product object or an alternate approach to be able to work with two way binding
product-form.component.ts
import { ProductService } from './../../product.service';
import { CategoryService } from './../../category.service';
import { Component, OnInit } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { take } from 'rxjs/operators';
#Component({
selector: 'app-product-form',
templateUrl: './product-form.component.html',
styleUrls: ['./product-form.component.css']
})
export class ProductFormComponent implements OnInit {
categories$;
product = {};
constructor(
private route: ActivatedRoute,
private router: Router,
private categoryService: CategoryService,
private productService: ProductService)
{
this.categories$ = categoryService.getCategories();
let id = this.route.snapshot.paramMap.get('id');
if (id) this.productService.get(id).pipe(take(1)).subscribe(p => this.product = p);
}
save(product){
this.productService.create(product);
this.router.navigate(['/admin/products']);
}
ngOnInit(): void {
}
}
product-form.html
<div class="row">
<div class="col-md-6">
<form #f = "ngForm" (ngSubmit) = "save(f.value)">
<div class="form-group">
<label for="title">Title</label>
<input #title = "ngModel" [(ngModel)]="product.title" name = "title" id = "title" type="text" class="form-control" required>
<div class="alert alert-danger" *ngIf = "title.touched && title.invalid">
Title is required
</div>
</div>
<div class="form-group">
<label for="price">Price</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input #price="ngModel" ngModel name ="price" id = "price" type="number" class="form-control" required>
</div>
<div class="alert alert-danger" *ngIf="price.touched && price.invalid">
<div *ngIf="price.errors.required">Price is required.</div>
<div *ngIf="price.errors.min">Price should be 0.</div>
</div>
</div>
<div class="form-group">
<label for="category">Category</label>
<select #category="ngModel" ngModel name="category" id = "category" type="text" class="form-control" required>
<option value=""></option>
<option *ngFor = "let c of categories$ | async" [value] = "c.name">
{{c.name}}
</option>
</select>
<div class="alert alert-danger" *ngIf = "category.touched && category.invalid">
Category is required.
</div>
</div>
<div class="form-group">
<label for="imageUrl">Image Url</label>
<input #imageUrl="ngModel" ngModel name= "imageUrl" id = "imageUrl" type="url" class="form-control" required>
<div class="alert alert-danger" *ngIf = "imageUrl.touched && imageUrl.invalid">
<div *ngIf = "imageUrl.errors.required">Image URL is required.</div>
<div *ngIf = "imageUrl.errors.url">Invalid URL</div>
</div>
</div>
<button class="btn btn-primary">Save</button>
</form>
</div>
<div class="col-md-6">
<div class="card" style="width: 18rem;">
<img [src]="imageUrl.value" class="card-img-top">
<div class="card-body">
<h5 class="card-title">{{title.value}}</h5>
<p class="card-text">{{price.value | currency:'USD':true}}</p>
</div>
</div>
</div>
</div>
product.service.ts
import { Injectable } from '#angular/core';
import { AngularFireDatabase } from '#angular/fire/database';
#Injectable({
providedIn: 'root'
})
export class ProductService {
constructor(private db: AngularFireDatabase) { }
create(product){
return this.db.list('/products').push(product);
// const itemsRef = this.db.list('products');
// return itemsRef.push(product);
}
getAll() {
return this.db.list('/products').valueChanges();
}
get(productId){
return this.db.object('/products/' + productId).valueChanges();
}
}
There are 2 ways to do this
Approach 1
Declare an interface
export interface Product {
title: string
}
Change the Component code section as follows
from
product = {};
To
product:Product;
Approach 2 - This can have side effects
Change HTML
From
<input #title = "ngModel" [(ngModel)]="product.title" name = "title" id = "title" type="text" class="form-control" required>
To
<input #title = "ngModel" [(ngModel)]="product['title']" name = "title" id = "title" type="text" class="form-control" required>
I fixed this error by changing a couple of things in 'tsconfig.json' file.
First remove "strict":true and add instead of it "noImplicitUseStrict":true
(Don't forget adding the comma)
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
}
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.
In angular 5 I am trying to add new row and remove row functionality. For add new row and delete row I have taken code from here. Everything is working fine but when I am trying to get the user entered value in the new row fields I am not getting those values.
I am getting the value for Event Name. But for other 3 fields like Package Name, Package Price, Max Purchase Limit, I am not getting them.
Here is the code what I have tried.
my event-create.component.html looks like this
<div class="container col-md-12">
<h1 class="page-header">Create Event</h1>
<div class="row show-hide-message">
<div [ngClass]= "messageClass">{{message}}</div>
</div>
<form [formGroup] = "form" (ngSubmit)="onEventSubmit()">
<fieldset>
<div class="form-group">
<label for="eventname">Event Name</label>
<div class='form-group' [ngClass]="{'has-error': form.controls.eventname.errors && form.controls.eventname.dirty,
'has-success': !form.controls.eventname.errors
}">
<input type="text" class="form-control" autocomplete="off" placeholder="Event Name" formControlName="eventname">
<ul class="help-block">
<li *ngIf="(form.controls.eventname.errors?.minlength ) && form.controls.eventname.dirty">Event name should be atleast 5 characters</li>
</ul>
</div>
</div>
<h4>Package Price</h4>
<hr>
<div class="row" formArrayName="sections">
<div class="col-md-12" *ngFor="let section of getSections(form); let i = index">
<div class="form-group col-md-5">
<label for="packagename">Package Name</label>
<input type="text" class="form-control" autocomplete="off" placeholder="Package Name" formControlName="packagename" >
</div>
<div class="form-group col-md-2">
<label for="packageprice">Package Price</label>
<input type="number" class="form-control" autocomplete="off" placeholder="Package Price" formControlName="packageprice" >
</div>
<div class="form-group col-md-2">
<label for="packagelimit">Max Purchase Limit</label>
<input type="number" class="form-control" formControlName="packagelimit" autocomplete="off" >
</div>
<div class="form-group col-md-1">
<br/>
<input type="button" (click)="addPackageRow()" class="btn btn-md btn-success" value="+" name="">
</div>
<div class="form-group col-md-1" *ngIf="getSections(form).length > 1">
<br/>
<input type="button" (click)="removeSection(i)" class="btn btn-md btn-error" value="-" name="">
</div>
</div>
</div>
<input [disabled]=!form.valid type="submit" class="btn btn-primary" value="Submit">
<pre>{{form.value | json}}</pre>
</fieldset>
</form>
</div>
and my event-create.component.ts looks like this
import { Component, OnInit } from '#angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '#angular/forms';
import { FormControlName } from '#angular/forms/src/directives/reactive_directives/form_control_name';
#Component({
selector: 'app-event-create',
templateUrl: './event-create.component.html',
styleUrls: ['./event-create.component.css']
})
export class EventCreateComponent implements OnInit {
form : FormGroup;
packagesArray: FormArray;
constructor(
private formBuilder : FormBuilder,
) { this.createEventForm() }
createEventForm() {
this.form = this.formBuilder.group({
eventname: ['', Validators.compose([
Validators.required,
Validators.minLength(5)
])],
packages: this.packagesArray
})
}
ngOnInit() {
this.form = new FormGroup({
eventname: new FormControl(''),
sections: new FormArray([
this.initSection(),
]),
});
}
initSection() {
return new FormGroup({
packagename: new FormControl(''),
packageprice: new FormControl(''),
packagelimit: new FormControl('')
});
}
initItemRows() {
return this.formBuilder.group({
itemname: ['']
});
}
onEventSubmit() {}
public addPackageRow() {
const control = <FormArray>this.form.get('sections');
control.push(this.initSection());
}
initOptions() {
return new FormGroup({
optionTitle: new FormControl('')
});
}
addSection() {
const control = <FormArray>this.form.get('sections');
control.push(this.initSection());
}
getSections(form) {
return form.controls.sections.controls;
}
public removeSection(i){
const control = <FormArray>this.form.get('sections');
control.removeAt(i);
}
}
Its not showing user entered value in html page also not doing any validation for the eventname field.
Here is the demo what I have done so far
https://stackblitz.com/edit/angular-3s5wwf
Can someone tell me what is going wrong here. Any suggestion or advice will be really appreciable. Thanks.
You need to add this in line 23 and 24
<div class="row" formArrayName="sections">
<div class="col-md-12" *ngFor="let section of getSections(form); let i = index" [formGroupName]="i">
you missed adding the formGroupName for the formArrayName
workinglink