Not able to store dynamic FormArray in FormGroup - html

I have a FormGroup which has three FormControl fields and one FormArray fields as shown in the figure below. The requirement is to take the manager name from user and once add button is pressed, manager details should be displayed in table. In table a remove button is provided, when remove button is pressed manager should be removed form the table and list. When the form is submitted list of managers should be saved. Everything works fine except formArray logic.
I tried to find a solution to this online (followed various links:- https://alligator.io/angular/reactive-forms-formarray-dynamic-fields/,
Angular 4 Form FormArray Add a Button to add or delete a form input row), but did not helped much. There is not much material on how to store formArray in formGroup. Please suggest.
Below is my code, please have a look:-
1. manager-create-modal.component.html
<div>
<form [formGroup]="createForm" (ngSubmit)="onFormCreation()">
<div class="row">
<div class="column">
<div class="form-inline">
<div class="form-group">
<label for="remote_access_method">Remote Access Method: <font color="orange"> *</font> </label>
<input type="text" size='38' class="form-control" formControlName="remote_access_method" >
</div>
</div>
<br>
<div class="form-inline">
<div class="form-group">
<label for="status">Current Status: <font color="orange"> *</font> </label>
<input type="text" size='38' class="form-control" formControlName="status">
</div>
</div>
<br>
<div class="form-inline">
<div class="form-group">
<label for="secregid">Registration ID:<font color="orange"> *</font> </label>
<input type="text" size='38' class="form-control" formControlName="secregid">
</div>
</div>
<br><br>
<div class="form-inline">
<div class="form-group">
<br><br>
<div formArrayName="manager_formArray">
Enter name: <input type="text" class="form-control" formControlName="MgrName" size='50' >
<button type="button" class="btn btn-primary btn-sm" (click)="addPM()">Add</button>
<br><br>
<table class="table table-hover">
<tr><th>#</th><th>Manager Name</th><th>Remove</th></tr>
<tr *ngFor="let pm of createForm.get('manager_formArray').value; let id = index">
<td>{{id+1}}</td>
<td>{{pm.MgrName}}</td>
<td>
<span class="table-remove">
<button type="button" class="btn btn-primary btn-sm" (click)="removeMgr(id)">Remove</button>
</span>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<br>
</div>
<br><br>
<div class="form-group">
<button class="btn btn-primary">Submit</button>
</div>
</form>
</div>
2. manager-create-modal.component.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormBuilder, FormArray, FormControl, Validators } from '#angular/forms';
#Component({
selector: 'app-manager-create-modal',
templateUrl: './manager-create-modal.component.html',
styleUrls: ['./manager-create-modal.component.css']
})
export class ManagerCreateModalComponent implements OnInit {
createForm: FormGroup;
manager_formArray: FormArray;
remote_access_method: FormControl;
status: FormControl;
secregid: FormControl;
constructor(private formBuilder: FormBuilder) { }
createFormControls(){
this.remote_access_method = new FormControl('');
this.status = new FormControl('');
this.secregid = new FormControl('');
this.manager_formArray = new FormArray([ this.createItem() ]);
}
createItem(): FormGroup {
return this.formBuilder.group({
MgrName: ''
});
}
createFormVariables(){
this.createForm = new FormGroup({
remote_access_method : this.remote_access_method,
status : this.status,
secregid : this.secregid,
manager_formArray: this.manager_formArray,
})
}
ngOnInit() {
this.createFormControls();
this.createFormVariables();
}
addPM(mgr: any): void {
console.log("inside addPM");
this.manager_formArray.push(this.formBuilder.group({MgrName:''}));
console.log("list after addition:"+this.manager_formArray.value);
for(let i = 0; i < this.manager_formArray.length; i++) {
console.log(this.manager_formArray.at(i).value);
}
}
get managerFormArray() {
return this.manager_formArray.get('MgrName') as FormArray;
}
onFormCreation(){
console.log("success")
}
}
The manager name is not displayed in the table and I keep on getting below error:-
ERROR Error: Cannot find control with path: 'manager_formArray ->
MgrName' at _throwError (forms.js:1731) at setUpControl
(forms.js:1639) at
FormGroupDirective.push../node_modules/#angular/forms/fesm5/forms.js.FormGroupDirective.addControl
(forms.js:4456) at
FormControlName.push../node_modules/#angular/forms/fesm5/forms.js.FormControlName._setUpControl
(forms.js:4961) at
FormControlName.push../node_modules/#angular/forms/fesm5/forms.js.FormControlName.ngOnChanges
(forms.js:4911) at checkAndUpdateDirectiveInline (core.js:9031)
at checkAndUpdateNodeInline (core.js:10299) at
checkAndUpdateNode (core.js:10261) at debugCheckAndUpdateNode
(core.js:10894) at debugCheckDirectivesFn (core.js:10854) inside
addPM manager-create-modal.component.ts:50 list after
addition:[object Object],[object Object]
manager-create-modal.component.ts:53 {MgrName: ""}
manager-create-modal.component.ts:53 {MgrName: ""}
I even don't understand why elements are not getting added to manager_formArray. Please help me out.

You have a few issues. First of all, it is better to move the Input which adds more FormGroups to your FormArray outside of the <div formArrayName="manager_formArray">- element. I created a new FormControl this.mgrNameInput = new FormControl(''); for this reason (see StackBlitz for more details).
You also need to add the message to the new entry when you press the Add-button, calling the addPM()-method:
addPM(){ // removed the argument, using the controller inside the method instead.
this.manager_formArray.push(this.formBuilder.group({MgrName:this.mgrNameInput.value}));
this.mgrNameInput.reset(); // reset the input field.
}
I also added the remove-method when removing an entry.
removeMgr(index: number){
this.manager_formArray.removeAt(index);
}
Please check the StackBlitz for the complete example

Related

Object is possibly 'null' in Reactive Forms Angular

I need your help!I am newbie in Angular Reactive Forms and I am trying to make simple form with validation using Angular's Reactive Forms. But I get an error in this part of my code (ngIf directive, property 'invalid'):
<div class="container">
<form class="card" [formGroup]="form" (ngSubmit)="submit()">
<h1>Angular Forms</h1>
<div class="form-control">
<label>Email</label>
<input type="text" placeholder="Email" formControlName="email">
<div
*ngIf="form.get('email').invalid"
class="validation">
</div>
</div>
<div class="form-control">
<label>Пароль</label>
<input type="password" placeholder="Пароль" formControlName="password">
<div class="validation"></div>
</div>
<div class="card">
<h2>Адрес</h2>
<div class="form-control">
<label>Страна</label>
<select>
<option value="ru">Россия</option>
<option value="ua">Украина</option>
<option value="by">Беларусь</option>
</select>
</div>
<div class="form-control">
<input type="text">
</div>
<button class="btn" type="button">Выбрать столицу</button>
</div>
<div class="card">
<h2>Ваши навыки</h2>
<button class="btn" type="button">Добавить умение</button>
<div class="form-control">
<label></label>
<input type="text">
</div>
</div>
<button class="btn" type="submit" [disabled]="form.invalid">Отправить</button>
</form>
</div>
TypeScript code:
import { Component, OnInit } from '#angular/core';
import { FormControl, FormGroup, Validators } from '#angular/forms';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.less']
})
export class AppComponent implements OnInit {
title = 'forms';
form!: FormGroup;
ngOnInit() {
this.form = new FormGroup({
email: new FormControl('',
[Validators.email,
Validators.required]),
password: new FormControl(null,
[Validators.required,
Validators.minLength(8)])
})
}
submit() {
console.log('Form submitted', this.form);
const formData = { ...this.form.value };
console.log('Form Data:', formData);
}
}
I use Angular 12 and I followed guide on Udemy, so this is very strange, because my mentor's code works correct. I created FromGroup and FormControls, gave them names, so I am really confused about this error. Do you have any ideas how can I fix it?
The Object is possibly 'null' error can happen due to strict type checking and can be solved in 2 ways:
Either assert that you are absolutely sure that can never be null, by using the ! (not null assertion operator)
Use the ? (optional chaining operator) to stop an eventual error from happening in case the object is indeed null
So you can replace the if statement with form.get('email')?.invalid and it should work. A similar question has been asked here.

Pass value from Typescript to HTML. Error; [object HTMLInputElement]

I try to pass array values from a .ts file to a html file in a Angular Project.
The HTML file is:
<body>
<section id="login" class="d-flex justify-content-center flex-column align-items-center">
<form>
<div class="form-group">
<label>Firmware</label>
<input #firmware class="form-control" type="text" value= {{firmware}} readonly>
</div>
<div class="form-group">
<label>Bootloader</label>
<input #bootloader class="form-control" type="text" value= {{bootloader}} readonly>
</div>
</form>
</section>
</body>
and the .ts file is:
import { Component, OnInit } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Router, ActivatedRoute, ParamMap } from '#angular/router';
#Component({
selector: 'app-dual',
templateUrl: './dual.component.html',
styleUrls: ['./dual.component.scss']
})
export class DualComponent implements OnInit {
title = "";
constructor(private router: Router, private httpClient : HttpClient) { }
ngOnInit(): void {
var resultArray:number[] = [235,23]
var firmware = resultArray[0];
var bootloader = resultArray[1];
console.log("firmware",firmware);
console.log("bootloader",bootloader);
}
}
If i show the values via console.log they are shown right but if i try to show them in the HTML file the value in the textbox is "[object HTMLInputElement]"
Note: Im new in Angular and try to learn :)
Any tips?
Thank you!
Solution:
HTML:
<div class="form-group">
<label>Firmware</label>
<input class="form-control" type="text" value= {{firmware}} readonly>
</div>
<div class="form-group">
<label>Bootloader</label>
<input class="form-control" type="text" value= {{bootloader}} readonly>
</div>
TypeScript:
var resultArray:number[] = [235,23]
this.firmware = resultArray[0].toString();
this.bootloader = resultArray[1].toString();
console.log("firmware",this.firmware);
console.log("bootloader",this.bootloader);
I deleted my old answer, because I totally missed what was wrong. When you do this:
<input #firmware class="form-control" type="text" value= {{firmware}} readonly>
The #firmware creates a new local variable named firmware, which is equal to the HTML element it's contained in. It's that new local variable that is being displayed inside the curly braces, not the variable firmware which is defined in your DualComponent class.
Make sure you don't duplicate variable names like this, and the problem will go away.

I create simple form using angular template driven form

but I got this error,
Object is possibly 'null
<span *ngIf="name.errors.minlength">You must enter atleast 3 characters
why my error object is always null,minlength is not store in my error object what is the reason for that?
My hero.ts file is,
export class Hero {
constructor(
public id: number,
public name: string,
public email: string
) { }
}
My formone.component.ts file is,
import { Component, OnInit } from '#angular/core';
import {NgForm} from "#angular/forms";
import {Hero} from "../../hero"
#Component({
selector: 'app-formone',
templateUrl: './formone.component.html',
styleUrls: ['./formone.component.css']
})
export class FormoneComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
model = new Hero(1, 'chamara','chamara#gmail.com');
submitted = false;
onSubmit() { this.submitted = true; }
get diagnostic() { return JSON.stringify(this.model); }
}
My formone.component.html file is,
<div class="container mt-3">
<div class="row">
<div class="col-md-2">
</div>
<div class="col-md-8">
<h4>Template Driven Forms</h4>
<form #formone="ngForm" (ngSubmit)="onSubmit()">
{{diagnostic}}
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name"
required
[(ngModel)]="model.name" name="name" #name="ngModel" minlength="3" #x>
</div>
<div *ngIf="!name.valid" class="alert alert-danger" >
<span *ngIf="name.errors.minlength">You must enter atleast 3 characters</span>
<span *ngIf="name.errors.required">This field required</span>
</div>
<div class="form-group">
<label for="alterEgo">Email</label>
<input type="email" class="form-control" id="alterEgo"
[(ngModel)]="model.email" name="alterEgo">
</div>
<br>
<button type="submit" class="btn btn-primary" [disabled]="!formone.form.valid">Submit</button>
</form>
</div>
<div class="col-md-2">
</div>
</div>
</div>
Plz help me to fix this,
Your Approach seems to be fine (at least for me), might you need to take care of few things
Remove extra #X from input code (this is causing issue by misleading error checking) and try to add name?.errors?.minlength just cross check name exists while checking errors.
<input type="text" class="form-control" id="name" required
[(ngModel)]="model.name" name="name" #name="ngModel" minlength="3">
<span *ngIf="name?.errors?.minlength">You must enter atleast 3 characters</span>
You can check more about template driven error handling here:
https://medium.com/swlh/form-validation-with-angular-template-driven-forms-8e0756cbec5
Happy Coding.. :)

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.

How to implement a pseudo event for a custom component in Angular?

I have a custom component that contains an input and a button like this
<div class="input-group">
<input type="text" class="form-control form-control-sm"
(input)="change($event)"
ngbDatepicker #d="ngbDatepicker" required
#datef />
<div class="input-group-append">
<button type="button" class="btn btn-sm btn-success" (click)="d.toggle()" type="button">
<i class="fa fa-calendar"></i>
</button>
</div>
</div>
I'd like it to have some funcionality so when the user press enter on the input it should emit a pseudo event
<custom-datepicker (keyup.enter)="handleKeyboard($event)"></custom-datepicker>
I've tried with #HostListener, but I'm getting errors about too much recursion, please, help me
You can just simple use the concept of event emitters wherein you can emit an event from your custom component to your parent component
----Custom Component Html----
<div class="input-group">
<input type="text" class="form-control form-control-sm"
(input)="change($event)"
ngbDatepicker #d="ngbDatepicker" required
#datef />
<div class="input-group-append">
<button type="button" class="btn btn-sm btn-success" (click)="d.toggle()" type="button">
<i class="fa fa-calendar"></i>
</button>
</div>
----Custom Component ts----
#Output()
customEvent = new EventEmitter();
change(event) {
this.customEvent.emit();
}
----Parent Component ----
<custom-datepicker (customEvent)="handleKeyboard($event)"></custom-datepicker>
You can approach this using Reactive Forms FormArray. You would attach an (keyup) or (keyup.enter) handler to the <input />. Within the handler for this keyup event, we push a new FormControl to a FormArray. This example uses FormBuilder to generate a FormGroup that contains a FormArray with a key of things. We use the push method of FormArray to add a new FormControl/AbstractControl within the handler for keyup.
Component:
import { Component } from '#angular/core';
import { FormBuilder, FormGroup, FormArray } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
myForm: FormGroup;
constructor(private fb: FormBuilder) {
this.createForm();
}
onEnter() {
this.addThing();
}
get things() {
return this.myForm.get('things') as FormArray;
}
private createForm() {
this.myForm = this.fb.group({
things: this.fb.array([
// create an initial item
this.fb.control('')
])
});
}
private addThing() {
this.things.push(this.fb.control(''));
}
}
Template:
<form [formGroup]="myForm">
<div formArrayName="things">
<div *ngFor="let thing of things.controls; let i=index">
<label [for]="'input' + i">Thing {{i}}:</label>
<input type="text" [formControlName]="i" [name]="'input' + i" [id]="'input' + i" (keyup.enter)="" />
</div>
</div>
</form>
At a very basic level you can loop through each in the form array using the controls property of the respective FormArray element and the value using the value property:
<ul>
<li *ngFor="let thing of things.controls">{{thing.value}}</li>
</ul>
Here is a StackBlitz(https://stackblitz.com/edit/angular-r5zmbg) demonstrating the functionality.
Hopefully that helps!