Dynamic form using *ngFor and submitting values from it - html

I have a stackblitz as a guide.
I am wanting to display a list of material cards that I click an 'edit' button, to which I can edit text fields, and when I click on the 'save' icon, it of course saves by triggering an function etc.
I am struggling however to get to grips with how this all works within Angular and the Material nature of my app.
html
<form id="myForm" [formGroup]="thisIsMyForm">
<mat-card [formGroup]="x" *ngFor="let x of data; let i = index">
<mat-form-field>
<label for="{{x.name}}">Name</label>
<input formControlName="name" id="{{x.name}}" matInput value="{{x.name}}">
</mat-form-field>
<mat-form-field>
<label for="{{x.type}}">Type</label>
<input formControlName="type" id="{{x.type}}" matInput value="{{x.type}}"/>
</mat-form-field>
</mat-card>
</form>
ts
import { Component, ViewChild } from '#angular/core';
import {MatSnackBar} from '#angular/material';
import {FormArray, FormBuilder, FormGroup, Validators} from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
thisIsMyForm: FormGroup
data = [
{name:"one", type:"one"},
{name:"two", type:"two"},
{name:"three", type:"three"},
];
constructor(private formBuilder: FormBuilder) {}
onSubmit() {
// Here I would like to be able to access the values of the 'forms'
}
}

You are diving into the deep end for sure, trying to build a dynamic reactive form within the scope of an *ngFor is a challenge. I will walk you through it the best I can.
You will need to create an array for controls, in your constructor create your form setting formArrayName as an empty array using this.formBuild.array([])... call this whatever you want, I just used formArrayName for demonstration purposes.
After the form is instantiated with an empty array call this.buildForm()
constructor(private formBuilder: FormBuilder) {
this.thisIsMyForm = new FormGroup({
formArrayName: this.formBuilder.array([])
})
this.buildForm();
}
In your buildForm() iterate over your data[] and push controls for each index while assigning the default value and a default state of disabled.
buildForm() {
const controlArray = this.thisIsMyForm.get('formArrayName') as FormArray;
Object.keys(this.data).forEach((i) => {
controlArray.push(
this.formBuilder.group({
name: new FormControl({ value: this.data[i].name, disabled: true }),
type: new FormControl({ value: this.data[i].type, disabled: true })
})
)
})
console.log(controlArray)
}
Please Note: console.log(controlArray.controls) results in the following output... each index is a FormGroup with two controls name and type
0: FormGroup
1: FormGroup
2: FormGroup
In your html you will need to establish a container hierarchy that mimics the thisIsMyForm you just created.
parent:thisIsMyForm
child:formArrayName
grandchild:i as formGroupName
grandchild is important because it matches the console log of controlArray.controls in previous step
<form id="myForm" [formGroup]="thisIsMyForm">
<div [formArrayName]="'formArrayName'">
<mat-card *ngFor="let x of data; let i = index">
<div [formGroupName]="i">
Create edit and save buttons based on control disabled state
<button *ngIf="formControlState(i)" (click)="toggleEdit(i)">Enable Edit</button>
<button *ngIf="!formControlState(i)" (click)="toggleEdit(i)">Save</button>
Create methods in component to receive the index as an argument and handle logic to hide buttons and toggle input fields enable and disable state.
toggleEdit(i) {
const controlArray = this.thisIsMyForm.get('formArrayName') as FormArray;
if(controlArray.controls[i].status === 'DISABLED'){
controlArray.controls[i].enable()
}else{
controlArray.controls[i].disable()
}
}
formControlState(i){
const controlArray = this.thisIsMyForm.get('formArrayName') as FormArray;
return controlArray.controls[i].disabled
}
Submit button that console.log's the form value when clicked... also disable button while any of the input formControls are in enabled state.
<button [disabled]="thisIsMyForm.get('formArrayName').enabled" (click)="onSubmit()">Submit Form</button>
onSubmit() {
// Here I would like to be able to access the values of the 'forms'
console.log(this.thisIsMyForm.value)
}
Stackblitz
https://stackblitz.com/edit/dynamic-form-ngfor-otbuzn?embed=1&file=src/app/app.component.ts

Doing it with QueryList:
your html (this is an example):
<ng-container *ngFor="let x of data; let i = index">
<div class="ctr">
<span #names class="item">{{x.name}}</span>
<span #types class="item">{{x.type}}</span>
<span class="button" (click)="showData(i)">Show data</span>
</div>
</ng-container>
<h2>Selected values: </h2>
Selected name: {{selectedName}} <br>
Selected type: {{selectedType}}
some css just for the style
.ctr{
display: flex;
flex-direction: row;
margin-bottom: 20px;
}
.item{
margin-right:40px;
}
.button{
border: 1px solid black;
padding: 2px 5px 2px 5px;
cursor: pointer;
}
the component:
import { Component, QueryList, ViewChildren } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
#ViewChildren('names') names:QueryList<any>;
#ViewChildren('types') types:QueryList<any>;
selectedName: string;
selectedType: string;
data = [
{name:"one", type:1},
{name:"two", type:2},
{name:"three", type:3},
];
showData(index){
let namesArray = this.names.toArray();
let typesArray = this.types.toArray();
this.selectedName = namesArray[index].nativeElement.innerHTML;
this.selectedType = typesArray[index].nativeElement.innerHTML;
}
}
Working stackblitz: https://stackblitz.com/edit/angular-j2n398?file=src%2Fapp%2Fapp.component.ts

Related

How to block duplicate values while inserting value into an array?

How to prevent duplicate values during insert record into an array using angular6+
PARENTCOMPONENT.HTML:
<div class="form-group" style="margin-left:30px;margin-top:30px;">
<input type="text" class="form-control" placeholder="Posts" name="post" [(ngModel)]="post" #clearText>
</div>
<button type="submit" class="btn btn-sm btn-primary" (click)="AddServer(post)"
style="margin-left:30px;margin-top:10px;" (blur) = "clearText.value = ''">Click</button>
<app-child [childPost]="parentPosts"></app-child>
PARENTCOMPONENT.TS:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-parent',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.css']
})
export class ParentComponent implements OnInit {
post = '';
parentPosts: any[] = [];
constructor() { }
ngOnInit() {
}
AddServer(post)
{
this.parentPosts.push(post);
console.log(post);
}
}
CHILDCOMPONENT.HTML:
<div style="margin-left: 30px; margin-top:10px;" *ngFor="let p of childPost">
<p>{{p}}</p>
</div>
CHILDCOMPONENT.TS:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
#Input() childPost: any[] = [];
constructor() { }
ngOnInit() {
}
}
Hi guys the above code was insert data from parent component to child component using textbox values and button in that PARENCOMPONENT.TS code what i need is during the time of pushing value into an array the values must be unique if i post repeated value it throws an alert or error message the value was already inserted like that so please kindly help me to resolve this....
You could use Set data structure that will omit duplicate Insertions which is better then having an extra loop to verify if it's already in the array and then omit it.
parentPosts: Set = new Set();
// then use it like
this.parentPosts.add(post); // if post already exists it'll just not add it
Test if it contains it first
if (!this.parentPosts.includes(post)) {
this.parentPosts.push(post);
}

how to hide items from switch statement in angular

I'm learning Angular and wondering how I can't hide some item and show certain items only when the user select specific item in the drop down list.
For example, In my page, I have Chart TextBox, Text TextBox, Grid TextBox and a drop down list that contain Chart, Text, and Grid. when ever user select Text, I want to show only Text TextBox and hide the rest.
right now, the three chart type options are showing on drop drop list when ever I run the project but it's not doing anything when I select Text and also I got an error on my ngIf saying that
"Property 'text' does not exist on type 'ChartType'."
I would be really appreciate if can somebody teach me or help me.
The problem I have is in the project I found from from github, the data for drop down list is in the another file called chart.model.ts and it written like this
export class ChartUtil {
public static getChartTypeDisplay(type: ChartType): string {
switch (type) {
case ChartType.chart:
return "Chart"
case ChartType.text:
return "Text";
case ChartType.grid:
return "Grid";
default:
return "unknown";
}
}
}
and display it like this
design.component.ts
chartTypes: TypeListItem[] = [];
setupTypes() {
let keys = Object.keys(ChartType);
for (let i = 0; i < (keys.length / 2); i++) {
this.chartTypes.push({ value: parseInt(keys[i]), display: ChartUtil.getChartTypeDisplay(parseInt(keys[i])) })
}
html
<ng-container *ngIf="chart.chartTypes == chartTypes.text">
<mat-form-field appearance="fill">
<mat-label>Text</mat-label>
<input matInput />
</mat-form-field>
I can't uploaded the full project on stackblitz but I uploaded all the code from those file over https://stackblitz.com/edit/angular-ivy-dmf3vn
This is normally how you would tackle a ng-switch
Component Code (badly done)
import { Component, VERSION, OnInit } from '#angular/core';
export class ChartType {
chart = 1;
text = 2;
grid = 3;
}
export class TypeListItem {
public value = 0;
public display = '';
public chartType = -1;
}
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
public chartTypesEnum = new ChartType();
public chartTypes: TypeListItem[] = [];
ngOnInit() {
let keys = Object.keys(this.chartTypesEnum);
for (let i = 0; i < (keys.length ); i++) {
this.chartTypes.push(
{
value: parseInt(ChartType[keys[i]]),
chartType: this.chartTypesEnum[keys[i]],
display: `This is a ${keys[i]}`
})
}
}
}
HTML (again badly done but simple)
<ng-container *ngFor="let chart of chartTypes">
<ng-container [ngSwitch]="chart.chartType" >
<div *ngSwitchCase="chartTypesEnum.chart">Chart</div>
<div *ngSwitchCase="chartTypesEnum.grid">Grid</div>
<div *ngSwitchCase="chartTypesEnum.text">Text</div>
</ng-container>
</ng-container>
Example
https://stackblitz.com/edit/angular-ivy-bievwr
Example 2
https://stackblitz.com/edit/angular-ivy-jhv4bq
This solution will render an Angular Material dropdown List using Enum with the ChartTypes insted of the your switch.
The Component:
import { Component, OnInit } from '#angular/core';
export enum ChartTypes {
Chart = 'Chart',
Text = 'Text',
Grid = 'Grid',
}
#Component({
selector: 'app-select-by-type',
templateUrl: './select-by-type.component.html',
styleUrls: ['./select-by-type.component.css']
})
export class SelectByTypeComponent implements OnInit {
// create an enum variable to work with on the view
chartTypes = ChartTypes;
// initialize the dropdown to a default value (in this case it's Chart)
chartsValue: string = ChartTypes.Chart;
constructor() { }
ngOnInit() {
}
}
The View:
<mat-form-field appearance="fill">
<mat-select required [(value)]="chartsValue">
<mat-option *ngFor="let chartType of chartTypes | keyvalue" [value]="chartType.value">{{chartType.value}}
</mat-option>
</mat-select>
<mat-label>Chart Type</mat-label>
</mat-form-field>
<ng-container *ngIf="chartsValue === chartTypes.Text">
<mat-form-field appearance="fill">
<mat-label>Text</mat-label>
<input matInput />
</mat-form-field>
</ng-container>
<ng-container *ngIf="chartsValue === chartTypes.Chart">
Chart In Template
</ng-container>
<ng-container *ngIf="chartsValue === chartTypes.Grid">
Grid In Template
</ng-container>

How to pass the object value dynamically as parameter into new FormControl() in angular 8

I have a dropdown whose data is coming from loop.Here I need to get the selected value and selected text on page load and on click a span.I am using reactive form here.To make default selection I am using new FormControl(1) ,Every thing is working correct I am getting the result in console.But when I am removing first object of array from my json and reload the page its creating error,since I hardcoded to 1 in FormControl,if I change to FormControl(2) its working fine.But I need to pass it dynamically so that it can work with any number of object. Here is the code below and demo example I created https://stackblitz.com/edit/angular-lwrvvz?file=src%2Fapp%2Fapp.component.ts
app.component.ts
import { Component } from '#angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
registerForm: FormGroup;
statusdata: any;
constructor(private formBuilder: FormBuilder)
{
this.registerForm = this.formBuilder.group({
title: new FormControl(1)
});
}
ngOnInit() {
this.statusdata = [{"groupid":1,"groupname":"project1"},{"groupid":2,"groupname":"project2"},{"groupid":3,"groupname":"project3"}];
console.log('selected Value', this.registerForm.get('title').value);
console.log('selected name', this.statusdata.filter(v => v.groupid == this.registerForm.get('title').value)[0].groupname);
}
getSelectedVal(){
console.log('selected Value', this.registerForm.get('title').value);
console.log('selected name', this.statusdata.filter(v => v.groupid == this.registerForm.get('title').value)[0].groupname);
}
}
app.component.html
-----------------
<hello name="{{ name }}"></hello>
<p>
Start editing to see some magic happenss :)
</p>
<div>
<form [formGroup]="registerForm">
<select formControlName="title" class="form-control">
<option *ngFor="let grpdata of statusdata" value="{{grpdata.groupid}}">{{grpdata.groupname}}</option>
</select>
</form>
</div>
<p>home works!</p>
<div><span (click)="getSelectedVal()">Click here</span></div>
You can update the form to using patchValue method to select the first group in statusdata array. This way you don't have to hard-code anything.
Example: https://stackblitz.com/edit/angular-ggnhzq
(I've added a null condition for retrieving groupname in case there are 0 groups)

How to get check particular checkboxes by default based on some values in angular 7

I have a checkboxes and selectbox whose values are coming from loop,but here I need to get some checkboxes checked by default based on an array of object.Here checkbox and selectbox value is coming from usersg and usersr variable.But the checked and selected by default should be from variable usersg_checked and usersr_selected inside ngOnInit(). Here is the code below
home.component.html
<p *ngFor="let group of usersg"><input type="checkbox" checked="checked.id" value="{{group.id}}" />{{group.name}}</p>
<p><select><option *ngFor="let role of usersr" value="{{role.id}}">{{role.name}}</option></select></p>
home.component.html
import { Component, OnInit } from '#angular/core';
import { CommonserviceService } from './../utilities/services/commonservice.service';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
submitted = false;
usersg_checked:any;
usersr_selected:any;
constructor(private formBuilder: FormBuilder) {
}
public usersg = [{"id":1,"name":"test1"},{"id":2,"name":"test2"},{"id":3,"name":"test3"},{"id":4,"name":"test4"}];
public usersr = [{"id":1,"name":"test1"},{"id":2,"name":"test2"}];
ngOnInit() {
this.usersg_checked = [{"id":1,"name":"test1"},{"id":2,"name":"test2"}];
this.usersr_selected = [{"id":1,"name":"test1"}];
}
}
Add isChecked() method in component to check if a checkbox must be selected.
Component:
isChecked(id) {
return this.usersg_checked.some(item => item.id === id);
}
Template:
<p *ngFor="let group of usersg">
<input type="checkbox" [checked]="isChecked(group.id)" value="{{group.id}}" />{{group.name}}
</p>
For <select> elements better to use [(ngModel)].
Template:
<p><select [(ngModel)]="usersr_selected.id">
<option *ngFor="let role of usersr" value="{{role.id}}">{{role.name}}</option>
</select></p>
Component:
And change usersr_selected to an object.
ngOnInit() {
this.usersr_selected = {"id":1,"name":"test1"};
}
If usersr_selected is an array, use the first element of the array as NgModel.
ngOnInit() {
this.usersr_selected = this.usersr_selected[0];
}

Kind of recursive usage of an Component possible?

After searching for like two hours for a solution I decided to ask some pros suspecting the solution could be quite simple.
It is an Angular7 project.
I would like to have a "goal" in my goals component with a button "+". When you click that button I want to have annother goal being added to the page. So I want to click a button of the goal component to create a new goal, which is something like recursive to me.
goals.component.html:
<input type="text" value="Ich brauche einen Laptop für maximal 1000 Euro.">
<br/>
<br/>
<app-goal id="{{lastGivenId+1}}"></app-goal>
goals.component.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-goals',
templateUrl: './goals.component.html',
styleUrls: ['./goals.component.scss']
})
export class GoalsComponent implements OnInit {
lastGivenId: number = 0;
constructor() { }
ngOnInit() {
}
}
goal.component.ts and goal.component.html:
//Typescript code
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-goal',
templateUrl: './goal.component.html',
styleUrls: ['./goal.component.scss']
})
export class GoalComponent implements OnInit {
#Input() id : number;
constructor() { }
ngOnInit() {
}
onAddLowerGoal(currentGoalID:number){
// var goalElement = document.registerElement('app-goal');
// document.body.appendChild(new goalElement());
let newGoal = document.createElement("app-goal");
newGoal.setAttribute("id", "999");
let currentGoal = document.getElementById(currentGoalID.toString());
document.body.insertBefore(newGoal, currentGoal);
}
}
<html>
<div id="{{id}}" class="goal">goal{{id}}</div>
<button id="AddLowerGoal1" (click)="onAddLowerGoal(999)">+</button>
</html>
This way, it creates an app-goal element, but the div and button elements within the app-goal element is missing.
How can this problem be solved? Any help is welcome. Thanks in advance.
First glance: delete the html tags from your goal.component.html file.
Next: you can recursively add app-goal using angular. Inserting app-goal element the javascript way only adds the <app-goal></app-goal> object. It doesn't create an angular component. It doesn't bind your data.
Also if you're using Angular's #Input, you need to assign a component input with square braces. Do not use tags.
goals.component.html:
<input type="text" value="Ich brauche einen Laptop für maximal 1000 Euro.">
<br/>
<br/>
<app-goal [id]="lastGivenId+1"></app-goal>
goal.component.html:
<div id="{{id}}" class="goal">goal{{id}}</div>
<button id="AddLowerGoal1" (click)="onAddLowerGoal(999)">+</button>
<div *ngFor="let subGoal of subGoals">
<app-goal [id]="subGoal.id"></app-goal>
</div>
goal.component.ts:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-goal',
templateUrl: './goal.component.html',
styleUrls: ['./goal.component.scss']
})
export class GoalComponent implements OnInit {
#Input() id : number;
subGoals: Array<any> = [];
constructor() { }
ngOnInit() { }
onAddLowerGoal(currentGoalID: number){
this.subGoals.push({id: currentGoalID});
}
}
You can also use a service to store your goals and subgoals to access them later.
I think what you're looking for is a Reactive Form with FormArray with dynamically added form controls.
Take a look at this for eg:
import { Component } from '#angular/core';
import { FormControl, FormGroup, FormArray, FormBuilder } from '#angular/forms';
#Component({...})
export class GoalsComponent {
goalsForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.goalsForm = this.fb.group({
goals: this.fb.array([])
});
}
onFormSubmit() {
console.log('Form Value: ', this.goalsForm.value);
}
get goals() {
return (<FormArray>this.goalsForm.get('goals')).controls;
}
addGoal() {
(<FormArray>this.goalsForm.get('goals')).push(this.fb.control(null));
}
}
And here's the template for this:
<h2>Goals:</h2>
<form [formGroup]="goalsForm" (submit)="onFormSubmit()">
<button type="button" (click)="addGoal()">Add Goal</button>
<hr>
<div *ngFor="let goal of goals; let i = index;" formArrayName="goals">
<div>
<label for="goal">Goal {{ i + 1 }}: </label>
<input type="text" id="goal" [formControlName]="i">
</div>
<br>
</div>
<hr>
<button>Submit Form</button>
</form>
Here's a Sample StackBlitz for your ref.