Dynamic Form issue with Reactive Form - html

I want to create a Dynamic input field on button press and successfully I am able to create new input field on button press with the help of reactive form but now I want to set predefined value in input field as per API data. Like if from API if I am getting 6 names, I want to create that 6 input fields on Init and then I want to able to new input field on button press. Here is my code which I tried, I am able to create new input field but not able create input fields as per API data on Init.
Component.ts
export class ScreenmodalComponent implements OnInit {
#Input() screendetails;
personalForm : FormGroup;
arrayItems: any = [];
screenList: string;
constructor(private activeModal: NgbActiveModal, private formbuilder: FormBuilder, private pagesService: PagesService, private utilService: UtilService) { }
ngOnInit() {
this.personalForm = this.formbuilder.group({
other: this.formbuilder.array([ this.addanotherForm()])
});
// Api call
this.pagesService.GetScreens('').then(res => {
console.log('get screens api', res);
for (let i = 0; i < res['data']['length']; i++) {
this.arrayItems = res['data'][i]['name'] //names which I want to set as input field value and generate input fields for every name
}
}).catch(error => {
this.utilService.showError('Something went wrong', 'Error');
console.log('err is', error);
});
}
addanother():void {
(<FormArray>this.personalForm.get('other')).push(this.addanotherForm());
}
addanotherForm(): FormGroup{
return this.formbuilder.group({
screenname: [''],
});
}
clear(i : number){
(<FormArray>this.personalForm.get('other')).removeAt(i);
}
onSubmit(): void {
console.log(this.personalForm.value);
}
closeModel() {
this.activeModal.close();
}
}
Componenent.html code
<div class="custom_modal pb-3">
<div class="modal-header border-0 p-1 pl-3 pr-3 d-flex justify-content-between align-items-center">
<h3 class="m-0">Project: {{screendetails.name}}</h3>
<button type="button" class="close" data-dismiss="modal" (click)="closeModel()">×</button>
</div>
<div class="modal-body p-3">
<div class="body_top d-flex justify-content-between mb-4 mt-2 align-items-center">
<h3 class="title m-0">Add Screens</h3>
</div>
<form [formGroup]="personalForm" (ngSubmit)="onSubmit()">
<div formArrayName="other" class="form-group" *ngFor="let other of personalForm.get('other').controls; let i = index" >
<div [formGroupName] = "i">
<div class="screen_row mb-4">
<div class="row">
<div class="col-md-3 col-sm-3 d-flex align-items-center">
<label>Screen Name</label>
</div>
<div class="col-md-8 col-sm-8 pl-0 pl-sm-3">
<input type="text" class="form-control rounded-0" formControlName="screenname" name="screenname">
</div>
<div class="col-md-1 col-sm-1 d-flex align-items-center justify-content-center pl-0 pl-sm-3">
<button type="button" class="close" (click)="clear(i)">×</button>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer border-0 d-table w-100">
<button type="button" class="btn-primary float-left" (click)="addanother()">Add New Screen</button>
<div class="float-right">
<button type="button" class="btn-primary mr-3" data-dismiss="modal" (click)="onSubmit();">Apply</button>
<button type="button" class="btn-primary cancle" data-dismiss="modal" (click)="closeModel()">Cancel</button>
</div>
</div>
</form>
</div>
</div>
This is my code. Please help me setting up by default input fields as per the number of names in API call.Thannk you

Normally, you change your function addAnotherForm to allow pass data
addanotherForm(data:any): FormGroup{
return this.formbuilder.group({
screenname: [data],
});
}
In your case you must change the function addanother
addanother(data:any):void {
(<FormArray>this.personalForm.get('other')).push(this.addanotherForm(data));
}
So, your ngOnInit can be like.
//at first an Empty FormArray
this.personalForm = this.formbuilder.group({
other: this.formbuilder.array([])
});
// Api call
this.pagesService.GetScreens('').then(res => {
console.log('get screens api', res);
for (let i = 0; i < res['data']['length']; i++) {
this.arrayItems = res['data'][i]['name']
this.addanother(res['data'][i]['name'] //fill the array
}
}).catch(error => {
this.utilService.showError('Something went wrong', 'Error');
console.log('err is', error);
});
Well, really you can simply all the code -and really you needn't this.arrayItems- and we use a map
this.pagesService.GetScreens('').then(res => {
this.personalForm=new FormGroup({
other:new FormArray(
res['data'].map(d>=>{
return new FormGroup({
screenname:new FormControl(d.name))
})
})
)
})
}).catch(error => {
this.utilService.showError('Something went wrong', 'Error');
console.log('err is', error);
});
NOTE: You needn't make a ForGroup if you only want yo control a FormArray, and your formArray can be only a formArray of formControls not a FormArray of FormGroup

Related

ERROR Error: Cannot find control with path: 'stocks -> stockName'

Hi I have a error trying to create dynamic form with reactive form module
Here is my code for html
<div class="container-fluid">
<h2>Stock Rebalancer</h2>
<div class="form-group">
<input class="form-control" placeholder="Total Capital" [(ngModel)]="buyingPower">
</div>
<div class="form-group">
<input class="form-control" placeholder="Buying power" [(ngModel)]="buyingPower">
</div>
<form [formGroup]="stockForm">
<div formArrayName="stocks">
<div class="form-group" *ngFor="let stock of stockForm.get('stocks')['controls']; let i = index">
<div class="form-row">
<div class="col">
<input formControlName="stockName" class="form-control" placeholder="stock name">
</div>
<div class="col">
<input formControlName="currentTotal" class="form-control" placeholder="$current total">
</div>
<div class="col">
<input formControlName="stockPercentage" class="form-control" placeholder="percantage%">
</div>
<div class="col">
<input formControlName="stockPrice" class="form-control" placeholder="$stock price">
</div>
<button class="btn btn-light" type="button" title="remove Stock" (click)="onRemoveStock(i)">X</button>
</div>
</div>
</div>
</form>
<button class="btn btn-primary" type="button" title="Add Stock" (click)="addStock()">Add Stock</button>
<!-- <button class="btn btn-primary" type="button" title="Add Stock" (click)="onRebalance()">Rebalance</button> -->
</div>
It has four controls, stockName, stockPrice, currentTotal, and stockPercentage. With four of these controls can build an item in a form array.
Below is the code for component
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, FormArray, FormControl } from '#angular/forms';
import { Plan } from 'src/app/interface/plan';
#Component({
selector: 'app-stock-component-calculator',
templateUrl: './stock-component-calculator.component.html',
styleUrls: ['./stock-component-calculator.component.css']
})
export class StockComponentCalculatorComponent implements OnInit {
constructor(private formBuilder: FormBuilder) { }
stockForm: FormGroup;
buyingPower: number;
plans: Plan[]
ngOnInit(): void {
this.stockForm = this.formBuilder.group({
stocks: this.formBuilder.array([])
});
const stockControl = this.stockForm.get('stocks')['controls'];
for (var i = 0; i < 6; i++) {
stockControl.push(this.formBuilder.group({
stockName: new FormControl(''),
currentTotal: new FormControl(0),
stockPercentage: new FormControl(0),
stockPrice: new FormControl(0),
}));
}
}
createStock(): FormGroup {
return this.formBuilder.group({
stockName: new FormControl(''),
currentTotal: new FormControl(0),
stockPercentage: new FormControl(0),
stockPrice: new FormControl(0),
});
}
addStock() {
const stockControl = this.stockForm.get('stocks')['controls'];
stockControl.push(this.createStock());
}
onRemoveStock(index) {
const stockControl = this.stockForm.get('stocks')['controls'];
stockControl.removeAt(index);
}
}
I got the error as following:
ERROR Error: Cannot find control with path: 'stocks -> stockName'
ERROR Error: Cannot find control with path: 'stocks -> currentTotal'
I am wondering what is the mistake that I made here.
Thank you for your help!
I fixed my issue with changing the
*ngFor="let stock of stockForm.get('stocks')['controls']; let i = index"
to
*ngFor="let stock of stocks; let i = index"
Also adding a new div of
<div [formGroup]="stock">
for the ts part, I declared
stocks: FormGroup[]
this.stocks = <FormGroup[]>(this.stockForm.controls.stocks as FormArray).controls;
Since it is a nested structure FormGroup -> FormArray -> FormGroup, so it takes me a bit of time to figure out.

set default value of Select option in angular update form

I'm working on an update task, I have a list of objects displayed in datatable and I want to execute the update process with a modal contain a form with an input and select options, when click the button to display the form, the input take the first attribute of my objectn but the problem is that the select option does not take a default value which is should be the second attribute of object !
<a data-toggle="modal" [attr.data-target]="'#modal-centered' + index"><i class="la la-pencil edit"
(click)="patchValue(rowData)"></i></a>
<div [attr.id]="'modal-centered' + index" class="modal fade">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{rowData.name}}</h4>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">×</span>
<span class="sr-only">Fermer</span>
</button>
</div>
<div class="modal-body">
<form [formGroup]="updateForm">
<div class="form-group">
<input type="text" formControlName="name" placeholder="Nom de la commission"
class="form-control">
</div>
<div class="form-group">
<select name="president" class="col-lg-6 custom-select form-control rounded"
formControlName="president">
<option [ngValue]="null" disabled>Choisir un Président</option>
<option [value]="president._id" *ngFor="let president of presidents">
{{president.firstName}}
{{president.lastName}}</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal"
(click)="updateCommission(rowData._id)">Sauvegarder</button>
</div>
</div>
</div>
</div>
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { FormGroup, FormControl } from '#angular/forms';
#Component({
selector: 'app-commission',
templateUrl: './commission.component.html',
styleUrls: ['./commission.component.css']
})
export class CommissionComponent implements OnInit {
commissions: any[];
updateForm: FormGroup;
presidents: any[];
cols: any[];
loading: boolean = true;
constructor(
private http: HttpClient
) {
this.updateForm = new FormGroup({
name: new FormControl(''),
president: new FormControl('')
});
}
ngOnInit() {
this.http.get('/api/commissions').subscribe((commissions: any[]) => {
this.loading = false
this.commissions = commissions;
})
this.http.get('/api/users/byType/conseillerMunicipal').subscribe((presidents: any[]) => {
this.presidents = presidents;
})
this.cols = [
{ field: 'name', header: 'Nom' },
{ field: 'presidentFullName', header: 'President' },
];
}
updateCommission(commissionID) {
this.loading = true;
this.http.put('/api/commissions/' + commissionID, this.updateForm.value).subscribe(updatedCommission => {
this.loading = false;
this.commissions.filter(commission => commission._id === commissionID)[0] = updatedCommission;
})
}
patchValue(commission) {
debugger
this.updateForm.setValue({
name: commission.name,
president: commission.presidentFullName
})
}
}
This does the job and displays the presidents in a drop down. However, I also need to select the president of the object to updated by default .
Try this one:
this.updateForm = new FormGroup({
name: new FormControl(''),
president: new FormControl(null)
});
Accoring to your condition inner template, you compare option with null. But your default value for formControl is ''.

json object is not effecting to view

I am using angular Formio for dynamic screen creation, as part of that when I get the screen script from database, if I change the label of the particular element in ngOnInit() I am able to change.
but once the screen is displayed, then if I change the lable it is not effecting.
html
<Formio [form]="form" [submission]="submission" (submit)="onSubmit($event)"></formio>
<div class="mb-5">
<div class="row">
<div class="col-12 mb-5">
<div class="pull-right" style="float:right;">
<button class="btn save-button" (click)="clearData()">Clear data</button>
<button class="btn save-button" (click)="showData()">Show data</button>
</div>
</div>
</div>
</div>
component
ngOnInit() {
debugger;
this.triggerRefresh = new EventEmitter();
this.http.get('http://localhost:3000/angcomp/3')
.subscribe(
response => {
debugger;
this.data = response.json();
this.form = this.data;
this.form.components[0].label = 'Changed';//it is updating the lable in view
},
err => {console.error(err)}
);
}
showData() {
this.form.components[0].label = 'Again Changed'; // here it is not changing but in this.form.components[0].label value is displaying as 'Again Changed', but not effecting in front end
}
Try the refresh property in formio
In yout HTML:
<Formio [refresh]="triggerRefresh" [form]="form" [submission]="submission" (submit)="onSubmit($event)"></formio>
In your component:
showData() {
this.form.components[0].label = 'Again Changed';
this.triggerRefresh.emit({
form: this.form
});
}
https://github.com/formio/angular-formio/wiki/Form-Renderer#updating-forms-and-submissions

Angular how to post data with a button using (change) detector?

I am trying to post data to my REST server. When I use a (change) it sends the data to my rest server. When I try to activate the method on my button it doesnt even try to make a POST call. How can I solve this problem? I can't find anything about it.
HTML file:
<div class="container py-5">
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-6 mx-auto">
<div class="card rounded-0">
<div class="card-header">
<h3 class="mb-0">Organize</h3>
</div>
<div class="form-group">
<label for="codes" class="m-2">Choose a restaurant:</label>
<form #f="ngForm">
<input
type="text"
list="codes"
class="m-2"
(change)="saveCode($event)">
<datalist id="codes">
<option *ngFor="let c of codeList" [value]="c.name">{{c.name}}</option>
</datalist>
</form>
<button
type="submit"
class="btn btn-primary btn-lg float-none m-2"
id="btnAanmaken"
routerLink="/menu"
(change)="saveCode($event)"
>Aanmaken</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Typescript file:
// Method to post data to backend
public saveCode(e): void {
const name = e.target.value;
const list = this.codeList.filter(x => x.name === name)[0];
this.restaurant.name = list.name;
this.restaurant.address = list.address;
console.log(list.name);
console.log(list.address);
// Additional information to the server content type
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
// Making an array with the values of the restaurant
const data = {
name: list.name,
address: list.address
};
console.log(data);
// POST method
this.http.post('http://localhost:8080/aquadine-jee/resources/restaurant',
JSON.parse(JSON.stringify(data)) , httpOptions)
// wait till it gets posted to the backend
.subscribe( // subscribe to observable http.post
res => {
console.log("response" + " " + res); // log results otherwise log error
},
err => {
console.log('Error occured');
}
);
}
I tried to call the method by:
<button
type="submit"
class="btn btn-primary btn-lg float-none m-2"
id="btnAanmaken"
routerLink="/menu"
(change)="saveCode($event)"
>Aanmaken</button>
and:
<button
type="submit"
class="btn btn-primary btn-lg float-none m-2"
id="btnAanmaken"
routerLink="/menu"
(click)="saveCode($event)"
>Aanmaken</button>
In addition to the answer by #Moslem, change the button type from submit to button
<button type="button" .. > instead of <button type="submit" ...>
Conflict using routerLink routerLink="/menu" and click event.
change routing when back response.
Inject router: Router class and use this.router.navigateByUrl(url: string)
Router
constructor(private router: Router){
}
public saveCode(e): void{
// POST method
this.http.post('http://localhost:8080/aquadine-jee/resources/restaurant',
JSON.parse(JSON.stringify(data)) , httpOptions)
// wait till it gets posted to the backend
.subscribe( // subscribe to observable http.post
res => {
console.log("response" + " " + res); // log results otherwise log error
this.router.navigateByUrl("/menu")
},
err => {
console.log('Error occured');
}
);
}

enable binding doesn't work

<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
<button class="btn btn-danger pull-right btn-block" data-bind="enable: DeletedEnable">Delete</button>
</div>
Even when the DeletedEnable is false, the enable property doesn't work, and the button is still enabled.
self.CurrentStatusIsDraft= ko.pureComputed(function () {
return false;
});
self.DeletedEnable = ko.pureComputed(function () {
return self.CurrentStatusIsDraft() ;
});
Based on your example https://jsfiddle.net/3rnt2zsc/ :
A) it's not a good practice using inline onClick.
Since you are using knockout simply use click event binding.
B) Try to put less javascript logic in your view.
C) Apply binding by using new instance of your model.
Example: https://jsfiddle.net/of8qfdvL/1/
View:
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="col-lg-offset-6 col-md-offset-6 col-lg-6 col-md-6 col-sm-12 col-xs-12">
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
<button class="btn btn-default btn-gb-default btn-block pull-right" data-bind="enable: ContinuedEnabled, click:OnContinue">Continue</button>
</div>
<button class="btn btn-danger pull-right btn-block" data-bind="enable: DeletedEnabled(), click:OnDelete">Delete</button>
<pre data-bind="text: ko.toJSON(DeletedEnabled, null, 2)"></pre>
</div>
</div>
Model:
var viewModel = function() {
var self = this;
self.SubmissionId = ko.observable(1234);
self.LastPageId = ko.observable(2);
self.ContinuedEnabled = ko.observable(true); // you can set the value based on your logic
self.OnContinue = function(){
console.log("SubmissionId:",self.SubmissionId());
console.log("LastPageId:",self.LastPageId());
}
self.OnDelete = function(){
console.log("SubmissionId:",self.SubmissionId());
}
self.CurrentStatusIsDraft= ko.pureComputed(function () {
return false;
});
self.DeletedEnabled = ko.pureComputed(function () {
return self.CurrentStatusIsDraft() ;
});
}
ko.applyBindings(new viewModel()); // apply a new instance of your model