Angular Reactive Form with dynamic fields - javascript

I'm currently battling with Angular form array.
I have a form in which I add the fields dynamically.
I have created the form object:
this.otherDataForm = this.fb.group({
});
I add the dynamic fields like this:
addField(field: CustomFormField): void {
this.otherDataForm.addControl(id_campo, new FormControl('', Validators.required));
}
I loop through those fields:
<form *ngIf="selectedProfile" [formGroup]="otherDataForm">
<div class="mb-3" *ngFor="let field of fields; let i = index">
<label>{{field.descripcion}}</label>
<input class="form-control" [formControlName]="field.id_campo" type="number">
</div>
</form>
But I can't seem to get control of the errors of each field to show a validation message if the field is required.
Anyone can help me with this one?
Maybe there is a better way to do this.

well, I feel more comfortable using directly the constructor of formControl and formGroup
fields=[{id:'one',label : 'one',value:1},{id:'two',label : 'two',value:2}]
form=new FormGroup({})
ngOnInit()
{
this.fields.forEach(x=>{
this.form.addControl(x.id,new FormControl(x.value,Validators.Required))
})
}
<form [formGroup]="form">
<div *ngFor="let field of fields">
<input [formControlName]="field.id">
<div class="error" *ngIf="form.get(field.id).touched &&
form.get(field.id).invalid">Required</div>
</div>
</form>
{{form?.value|json}}
But you can use directily [formControl] in the input
<form [formGroup]="form">
<div *ngFor="let field of fields">
<label>{{field.label}}</label>
<input [formControl]="form.get(field.id)">
<div class="error" *ngIf="form.get(field.id).touched &&
form.get(field.id).invalid">Required</div>
</div>
</form>
Even, you can iterate over form.controls|keyvalue
<form [formGroup]="form">
<div *ngFor="let control of form.controls |keyvalue;let i=index">
<label>{{fields[i].label}}</label>
<input [formControl]="control.value">
<div class="error" *ngIf="control.value.touched &&
control.value.invalid">Required</div>
</div>
</form>
see stackblitz

Related

What is the easiest way to make editForm in Angular?

In my database i have many users which has many recipes.
Every recipe has some properties and collection of ingredients.
Below is screenshot
Recipe with all properties
So when user display recipe to edit on page should appear (form) recipe with loaded current data. This is kind of working because i can see the data but i think it's no done good.
I have form which is working fine without array (ingredients). Could you tell me how i should add ingredients to my edit form?
I'd be grateful if you see at my code and give me feedback and hints what i should change.
export class RecipeEditComponent implements OnInit {
#ViewChild('editForm') editForm: NgForm;
recipe: IRecipe;
photos: IPhoto[] = [];
ingredients: IIngredient[] = [];
uploader: FileUploader;
hasBaseDropZoneOver = false;
baseUrl = environment.apiUrl;
currentMain: IPhoto;
constructor(private route: ActivatedRoute, private recipeService: RecipeService,
private toastr: ToastrService) { }
ngOnInit(): void {
this.loadRecipe();
}
loadRecipe() {
this.recipeService.getRecipe(this.route.snapshot.params.id).subscribe(recipe => {
this.recipe = recipe;
this.initializeUploader();
})
}
updateRecipe(id: number) {
this.recipeService.editRecipe(id, this.recipe).subscribe(next => {
this.toastr.success('Recipe updated successfully');
this.editForm.reset(this.recipe);
}, error => {
this.toastr.error(error);
});
}
}
HTML
<div class="container mt-4 border" *ngIf="recipe">
<form #editForm="ngForm" id="editForm" (ngSubmit)="updateRecipe(recipe.id)" >
<h5 class=" text-center mt-2">Recipe details:</h5>
<div class="form-group mt-3">
<label for="city">Name</label>
<input class="form-control" type="text" name="name" [(ngModel)]="recipe.name">
</div>
<div class="form-group">
<app-ingredient-editor [ingredients] = "recipe.ingredients"></app-ingredient-editor>
<div *ngFor="let ingredient of recipe.ingredients; let i = index">
<input class="form-control" type="text" name="{{ingredient.name}}" [(ngModel)]="ingredient.name">
<input class="form-control" type="text" name="{{ingredient.amount}}" [(ngModel)]="ingredient.amount">
</div>
</div>
<div class="form-group">
<br>
<p>Add recipes</p>
</div>
<h5 class=" text-center mt-4">Description</h5>
<angular-editor cols=100% rows="6" [placeholder]="'Your description'" [(ngModel)]="recipe.description" name="description"></angular-editor>
</form>
<button [disabled]="!editForm.dirty" form="editForm" class="btn btn-success btn-block mb-5 mt-5">Save changes</button>
</div>
For now it's look like:
Form on page
When i delete ingredient name while changing on the console i have following error:
recipe-edit.component.html:12 ERROR Error: If ngModel is used within a form tag, either the name attribute must be set or the form
control must be defined as 'standalone' in ngModelOptions.
Problem is that part of code:
<div *ngFor="let ingredient of recipe.ingredients; let i = index">
<input class="form-control" type="text" name="{{ingredient.name}}" [(ngModel)]="ingredient.name">
<input class="form-control" type="text" name="{{ingredient.amount}}" [(ngModel)]="ingredient.amount">
</div>
</div>
But i don't know how to make it working..
How to add add array to template-driven form?
In my case i need to display current ingredients and be able to edit them.
I have tried something like this :
<input class="form-control" type="text" name="ingredient[i].name" [(ngModel)]="ingredient[i].name">
<input class="form-control" type="text" name="ingredient[i].amount" [(ngModel)]="ingredient[i].amount">
But id doesn't work
The problem is that the property name on the form must be defined in order for angular to know which input to update. You're binding name to the same property that the editable model is set to which means the user can edit it and in fact delete it, which isn't good.
The solution is to change it to a unique value that doesn't change. This should work:
<div *ngFor="let ingredient of recipe.ingredients; let i = index">
<input class="form-control" type="text" name="name{{ingredient.id}}" [(ngModel)]="ingredient.name">
<input class="form-control" type="text" name="amount{{ingredient.id}}" [(ngModel)]="ingredient.amount">
</div>
</div>
Link to stackblitz showing it working: https://stackblitz.com/edit/angular-10-base-template-q243lw?file=src%2Fapp%2Fapp.component.html
Edit: fixed bug in original post and added link to stackblitz

ngIf an angular reactive form component value

I have a set of radio buttons. If a user selected the value "yes" I want to show an additional box on the form.
https://stackblitz.com/edit/angular-4bgahw?file=src/app/personal/personal.component.ts
HTML.component
<div formGroupName="radioButtonsGroup" class="form-group col-6 pl-0 pt-3">
<div class="form-check-inline" *ngFor="let item of personal.radioButtonsdata">
<label for="{{item.section}}" class="col-12 customradio"
><span>{{item.section}}</span>
<input [value]="item" id="{{item.section}}" type="radio" formControlName="selectedButton"/>
<span class="checkmark"></span>
</label>
</div>
<!-- <div class="col-md-8" *ngIf="selectedButton.control.item === 'yes'"> --> //my attempt to target above input value
<div class="col-md-8" >
<input type="text" formControlName="title" class="form-control" placeholder="Title">
</div>
</div>
Can anybody get this to work and show me what I am doing wrong here please?
You need to access the value of the form control:
*ngIf="form.get('radioButtonsGroup.selectedButton').value.section === 'yes'">
STACKBLITZ
Everything you write in the template is resolved against the corresponding class (or against template variables), so you have to refer to the JavaScript control like this:
*ngIf="form.controls['selectedButton'].value === 'yes'"
Call a function to set flag based on value of the radio button, (ngModelChange)="onRadiochange($event)"
Try like this:
Working Demo
.html
<input [value]="item" (ngModelChange)="onRadiochange($event)" id="{{item.section}}" type="radio" formControlName="selectedButton" />
<div class="col-md-8" *ngIf="showTitle">
<input type="text" formControlName="title" class="form-control" placeholder="Title">
</div>
.ts
onRadiochange(e) {
if(e.section == 'yes'){
this.showTitle = true
} else {
this.showTitle = false
}
}
It can also be done in one line like this:
<input [value]="item" (ngModelChange)="$event.section == 'yes' ? showTitle=true:showTitle=false" id="{{item.section}}" type="radio" formControlName="selectedButton" />
Whenever yes checkbox is selected, you have to display the title textbox.
In that case, change your code like this.
In personal.component.ts, add this variable.
yesSelected: boolean = true;
Also in ngOnInit(),
this.form.valueChanges.subscribe(val=>{
if(val.radioButtonsGroup.selectedButton.section === "yes")
this.yesSelected = true;
else
this.yesSelected = false;
});
In personal.component.html, rewrite your if condition like this.
<div class="col-md-8" *ngIf="yesSelected">
<input type="text" formControlName="title" placeholder="Title">
</div>
These changes will show the title textbox only when the yes check box is selected.

ngModel is reflecting to all textareas

I have multiple textareas (looping with ngFor and adding new divs with textareas inside). What i need is for every textarea to have separate ngModel and i don't want to directly bind this to property from object in dataArray - for example:
[(ngModel)]='data.note' or [(ngModel)]='data.feedback' .
This works but I don't have feedback property in dataArray so it won't for work for second textarea.
For example with my current implementation change in one textarea is reflecting in all other textareas. I tried with index approach but getting error:
ERROR TypeError: Cannot read property '1' of undefined
<div *ngFor="let data of dataArray; let index=index;trackBy:trackByIndex;">
<div class="card-body">
<form class="form">
<div class="form-body">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<textarea name="note" [(ngModel)]='selectedNote' class="form-control"
rows="2"></textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group">
<textarea name="feedback" [(ngModel)]='selectedFeedback' class="form-control" rows="4"></textarea>
</div>
</div>
</div>
</div>
</form>
</div>
With current code if i add some text in first textarea with name 'note' that change is reflected for all textareas with name 'note'. As mentioned tried with adding
[(ngModel)]='selectedFeedback[index]' but i am getting error.
Also tried with giving different names to textareas:
<textarea name="note{{index}}" [(ngModel)]='dataArray[index]' rows="2"></textarea> OR
<textarea name="note{{index}}" [(ngModel)]='selectedNote' rows="2"></textarea>
but change is reflecting for each textarea again.
You can try it with any array, I am using data(n) function to return an Array of length n. In this example it's just for iteration
<div *ngFor="let item of data(8); let i = index">
<textarea [(ngModel)]='values[i]'></textarea>
</div>
// To reflect changes
<div *ngFor="let item of data(8); let i = index">
<div>{{ values[i] }}</div>
</div>
With TS
export class AppComponent {
values = [];
data(n) {
return Array(n);
}
}
Working example in Stackblitz.com
ngModel binds with the name property. So if you want to use multiple textarea try using different name attribute. You can iterate over like -
<ng-container *ngIf="let data of dataArray; index as i">
<textarea name="feedback_{{i}}" [(ngModel)]='selectedFeedback' class="form-control" rows="4"></textarea>
</ng-container>

Angular 4 : How to assign array of string into checkbox in reactive forms

I am trying display checkboxes for my user roles:
For eg. I have two user roles : 1.Admin 2.Employee
I have an array of roles in userObject:
user={
"name":"Bhushan",
"email":"bhushan#yaho.com",
"roles":['Admin','Employee']
}
I want to use reactive form to populate this model into form. I want to populate the roles array into read-only checkboxes i.e. when form loads, user can edit name & email but checkboxes will show admin toggeled if user has admin role or untoggled if he is not an admin. same can be said for employee role.
So far I have tried following:
<form [formGroup]="userForm" (ngSubmit)="onSubmit()" novalidate>
<div style="margin-bottom: 1em">
<button type="submit" [disabled]="userForm.pristine" class="btn btn-success">Save</button>
<button type="reset" (click)="revert()" [disabled]="userForm.pristine" class="btn btn-danger">Revert</button>
</div>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div class="form-group">
<label class="center-block">Email:
<input class="form-control" formControlName="email">
</label>
</div>
<div class="form-group" *ngFor="let role of roles;let i=index">
<label>
// I tried this, but it doesn't work
<!--<input type="checkbox" [name]="role" [(ngModel)]="role">-->
{{role}}
</label>
</div>
</form>
<p>userForm value: {{ userForm.value | json}}</p>`
Any Inputs?
Perhaps do something like the following. Build your form, and stick the roles in a form array:
this.userForm = this.fb.group({
name: [this.user.name],
roles: this.fb.array(this.user.roles || [])
});
// OPTIONAL: put the different controls in variables
this.nameCtrl = this.userForm.controls.name;
this.rolesCtrl = this.userForm.controls.roles.controls;
and the roles array you are iterating in the template could look like this:
roles = ['Admin', 'Employee','Some role 1', 'Some role 2']
and in your iteration just compare and set the role in roles array as checked in case it matches a value in the form array. Use safe navigation operator, as we know that the roles array is probably longer that the form array, so that an error won't be thrown trying to read an index that doesn't exist:
<div class="form-group" *ngFor="let role of roles;let i=index">
<input [checked]="role == rolesCtrl[i]?.value"
[disabled]="true" type="checkbox">{{role}}
</div>
DEMO

textbox validation for number and required in repeating mode angular js

Please refer below link
https://plnkr.co/edit/9HbLMBUw0Q6mj7oyCahP?p=preview
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.NDCarray = [{val: ''}];
$scope.NDCadd = function() {
$scope.NDCarray.unshift(
{val: ''}
);
};
$scope.data = angular.copy($scope.NDCarray);
$scope.NDCcancel=function(){debugger
$scope.NDCarray=$scope.data;
}
$scope.NDCdelete = function(index) {
if(index != $scope.NDCarray.length -1){
$scope.NDCarray.splice(index, 1);
}
};
});
It contains the textbox with add button. I have added validation for number and required field, it is working fine. but when i click add button it will create another textbox with entered value that time it showing the validation message for all the textboxes , i don't want to show validation message for all the textboxes. need to show validation for corresponding textbox only. that means when i enter something wrong in second textbox it is showing message to that textbox only.refer below screenshot.
validation message displaying for all textboxes.that should display for only one textbox.
Working plnkr : https://plnkr.co/edit/f4kAdZSIsxWECd0i8LDT?p=preview
Your problem is in your HTML, to get independant fields you must :
Move outside the form of the ng-repeat
Provide a dynamic name using $index on your fields, because name is what make each fields independant on the validation.
Here is the final HTML from the plnkr i didn't touch at all the javascript :
<body ng-controller="MainCtrl">
<form name="myForm">
<div ng-repeat ="ndc in NDCarray">
<div class="col-sm-4 type7" style="font-size:14px;">
<div style="margin-bottom:5px;">NDC9</div>
<label>Number:
<input type="number" ng-model="ndc.value"
min="0" max="99" name="{{'input_'+$index}}" required>
</label>
<div role="alert">
<span class="error" ng-show="myForm.input.$dirty && myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.number">
Not valid number!</span>
</div>
<tt>value = {{example.value}}</tt><br/>
<tt>myForm['input_{{$index}}'].$valid = {{myForm['input_'+$index].$valid}}</tt><br/>
<tt>myForm['input_{{$index}}'].$error = {{myForm['input_'+$index].$error}}</tt><br/>
</div>
<div class="col-sm-4 type7 " style="font-size:14px;">
<div style="padding-top:20px; display:block">
<span class="red" id="delete" ng-class="{'disabled' : 'true'}" ng-click="NDCdelete($index)">Delete</span>
<span>Cancel </span>
<span id="addRow" style="cursor:pointer" ng-click="NDCadd()">Add </span>
</div>
</div>
</div>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
</form>
</body>
Couple of changes:
If you add "track by $index" to your ng-repeat it will make each group of elements unique so that you don't have to worry about deriving unique names for elements.
Your validation on the number (myForm.ndcValue.$error.number) didn't work so I changed it to myForm.ndcValue.$error.max || myForm.ndcValue.$error.min
Also, you can throw an ng-form attribute directly on the div with your ng-repeat.
Like this:
<div ng-repeat="ndc in NDCarray track by $index" ng-form="myForm">
<div class="col-sm-4 type7" style="font-size:14px;">
<div style="margin-bottom:5px;">NDC9</div>
<label>Number:
<input type="number" ng-model="ndc.value" min="0" max="99" name="ndcValue" required>
</label>
<div role="alert">
<span class="error" ng-show="myForm.ndcValue.$dirty && myForm.ndcValue.$error.required">
Required!</span>
<span class="error" ng-show="myForm.ndcValue.$error.max || myForm.ndcValue.$error.min">
Not valid number!</span>
</div>
<tt>value = {{example.value}}</tt>
<br/>
<tt>myForm.ndcValue.$valid = {{myForm.ndcValue.$valid}}</tt>
<br/>
<tt>myForm.ndcValue.$error = {{myForm.ndcValue.$error}}</tt>
<br/>
</div>
Here's the working plunker.
I changed the input element name from "input" to "ndcValue" to be less confusing.

Categories