How to disable FormControl in FormArray in Angular 4 - javascript

I wrote the below code snippet, which I considered as it will disable the FormControl in a FormArray.
some.component.html
<form [formGroup]="testForm">
<div *ngFor="let num of countArr">
<input type="text" formNameArray="arrs">
</div>
</form>
some.component.ts
countArr = [1, 2, 3, 4, 5];
count = 5;
arrs;
testForm: FormGroup;
this.testForm = this.formBuilder.group(
arrs: this.formBuilder.array([])
);
this.arrs = this.testForm.get('arrs');
for (let i = 0; i < this.count; i++) {
this.arrs.insert(i, new FormControl({value: '', disabled: true}));
}
But after for execution completed, I checked the form and found nothing has been disabled. Can you please tell me where I am doing wrong??? :-)
Thank you for your help!!! :-)

First of all, this is how your html component should look like:
<form [formGroup]="testForm">
<div formArrayName="arrs">
<div class="form-group" *ngFor="let arrItem of testForm.get('arrs').controls; let i = index">
<input type="text" class="form-control" [formControlName]="i">
</div>
</div>
</form>
You do not need to iterate some random count variable inside your html component. You can iterate your added controls.
You may ask "Which controls exactly? They are not added yet!"
Well, this is why you programatically add those controls in ngOnInit:
ngOnInit() {
this.testForm = new FormGroup({
arrs: new FormArray([])
}
);
for (let i = 0; i < this.count; i++) {
const control = new FormControl(null, Validators.required);
(<FormArray>this.testForm.get('arrs')).push(control);
}
this.disableInputs();
}
This is the corrent syntax to initiate the FormArray and then create an initial control inside the for loop and push the newly created control to your array.
Note: there is a disableInputs() function call. This is where you disable your inputs programatically as well:
disableInputs() {
(<FormArray>this.testForm.get('arrs'))
.controls
.forEach(control => {
control.disable();
})
}
A working sample: stackblitz

If you want to enable Dynamic Input enable
form: FormGroup;
orders = [
{ id: 100, name: 'order 1' },
{ id: 200, name: 'order 2' },
{ id: 300, name: 'order 3' },
{ id: 400, name: 'order 4' }
];
constructor(private formBuilder: FormBuilder) {
const controls = this.orders.map(c => new FormControl(''));
this.form = this.formBuilder.group({
orders: new FormArray(controls)
});
this.form.get('orders').controls
.forEach(control => {
control.disable();
})
}
and html should look like this
<form [formGroup]="form" >
<label formArrayName="orders" *ngFor="let order of form.controls.orders.controls; let i = index">
<input type="text" [formControlName]="i">
{{orders[i].name}}
</label>
</form>

Use formArray control in the iteration to assign it in each input:
<form [formGroup]="testForm">
<div formArrayName="arrs">
<div *ngFor="let num of countArr; let idx = index">
<input type="text" [formControlName]="idx" [attr.disabled]="true">
</div>
</div>
</form>
You can refer to this article:
https://angular.io/guide/reactive-forms#display-the-formarray

To disable the FormControls of a FormArray, "reset" makes it easy.
this.formGroupHere.get(['formArrayHere']).reset({
disableFields: {
formControlHere: true,
otherFormControl: true
}
}
);

It's possible by disabling the formControl while initialization or updating as below:
I assume that, testForm is the formGroupName , arrs is the FormArrayName and inputValue is the formControlName
(<FormArray>this.testForm.get('arrs')).push(new FormGroup({
'inputValue': new FormControl({ value: '', disabled: true }, Validators.required),
}));
You have to remember that disabling the form input will not allow you to submit. Instead, you can use the readonly property as below.
<input readonly="readonly" type="text" />
This will help you get the input values from the form as well.
Source for readonly

Related

How to get the values of default checked checkbox

I was working on a project of multiple checkbox. There, I want the checkboxes to be checked from the start and the value to be in the form(I am using reactive form). The user can unselect the boxes according to their wish and the data will be stored accordingly. This is the stackblitz of the project. There I was able to make the checkbox checked from the beginning, but when I hit the submit button there is no data when I console-logged. I think this is some binding issue,but I couldn't figure out what is exactly the problem.
Can someone help?
Thanks in advance.
This is code:
<form [formGroup]="form" (ngSubmit)="submit()">
<div class="form-group">
<label for="website">Website:</label>
<div *ngFor="let web of websiteList">
<label>
<input
type="checkbox"
[value]="web.id"
(change)="onCheckboxChange($event)"
[checked]="web.isSelected"
/>
{{ web.name }}
</label>
</div>
</div>
<button class="btn btn-primary" type="submit">Submit</button>
</form>
form: FormGroup;
websiteList: any = [
{ id: 1, name: 'HDTuto.com', isSelected: true },
{ id: 2, name: 'HDTuto.com', isSelected: true },
{ id: 3, name: 'NiceSnippets.com', isSelected: true },
];
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
website: this.fb.array([], [Validators.required]),
});
}
ngOnInit() {}
onCheckboxChange(e: any) {
const website: FormArray = this.form.get('website') as FormArray;
console.log('checking ->', e);
if (e.target.checked) {
website.push(new FormControl(e.target.value));
console.log('website ->', website);
} else {
//console.log(e);
const index = website.controls.findIndex((x) => {
console.log('x.value ->', x.value);
console.log('target.value ->', e.target.value);
x.value === e.target.value;
});
website.removeAt(index);
}
}
submit() {
console.log(this.form.value);
}
https://stackblitz.com/edit/angular-ivy-qar4ph?file=src/app/app.component.ts
Pay attention to changes in template:
Added formArrayName attribute to checkboxes wrapper and formControlName attribute to input element.
Removed change and checked attributes
In the component ts file:
Added initial form array values
Added mapping to submit method
Removed onCheckboxChange method

How to use [(ngModel)] in ngFor with dynamic Name (Key)

I have angular form that are generated by NgFor on two different div. i want to make form fields unique.
both fields are not working individually
<div *ngFor="let configField of configFields; let i = index"><mat-form-field fxFlex fxFlex.lt-md="calc(50% - 16px)" >
<input matInput placeholder="Url" name="url_{{i}}" [(ngModel)]="headerFields.url" >
</mat-form-field>
<mat-form-field fxFlex fxFlex.lt-md="calc(50% - 16px)" >
<input matInput placeholder="Method" name="type_{{i}}" [(ngModel)]="headerFields.type">
</mat-form-field></div>
i want to add flag on field name for uniquely identified in (headerFields)Array.. like name="type_{{i}}" type_1 ,type_2
On initialization or assignment of configFields, you can initialize or assign to the headerFields an array of length equal to the one as configFields, with url and type set as empty. Something like the following:
const configFields = [1,2,3];
const headerFields = Array.from(configFields, (el) => ({ url: '', type: ''})) // Set to empty string or null as desired]
How about using form array instead?
file component.ts
export class AppComponent {
form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = fb.group({
config: fb.array([])
});
}
addNewAddressGroup() {
const add = this.form.get('config') as FormArray;
add.push(
this.fb.group({
url: [],
type: []
})
);
}
deleteAddressGroup(index: number) {
const add = this.form.get('config') as FormArray;
add.removeAt(index);
}
}
file html.ts
<div [formGroup]="form">
<div>
<button (click)="addNewAddressGroup()">Add</button>
</div>
<div
*ngFor="let addressGroup of form.get('config').controls;let i = index"
[formGroup]="addressGroup">
<div>Url <input formControlName="url" /></div>
<div>Type <input formControlName="type" /></div>
<button (click)="deleteAddressGroup(i)">Delete</button>
</div>
</div>
result <br />
<pre>
{{form.value | json}}
</pre>
You can check out this Demo

How to add input fields dynamically in angular 6

I need to add input fields to array of objects and a map which grows dynamically based on user's choice.
For e.g. InputForm has a list and a map which needs to be filled by user.
export class InputForm {
mapA: {};
listA: ListA[] = [];
}
export class ListA {
input1 : String
input2 : number
}
I need to show it on UI in such a way that input1, input2 and key, value for map of criteria is visible as the input field. The user fills all 4 input fields and clicks on the add button.
Then again same input fields should be editable for the user for the second input. This way he can build list and map and when he clicks on submit button array and map should have all the values filled before.
*ngFor doesn't work because the initial list is empty.
Assuming you are using Angular Reactive Form, you can use a combination of *ngFor and FormArray. You can start with an empty FormArray and add dynamically using the .push() method.
Here is a good and detailed example
// order-form.component.ts:
#Component({
selector: '...',
templateUrl: './order-form.component.html'
})
export class OrderFormComponent implements OnInit{
orderForm: FormGroup;
items: FormArray;
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.orderForm = this.formBuilder.group({
customerName: '',
email: '',
items: this.formBuilder.array([ this.createItem() ])
});
}
createItem(): FormGroup {
return this.formBuilder.group({
name: '',
description: '',
price: ''
});
}
addItem(): void {
this.items = this.orderForm.get('items') as FormArray;
this.items.push(this.createItem());
}
}
<!-- order-form.component.html -->
<div formArrayName="items"
*ngFor="let item of orderForm.get('items').controls; let i = index;">
<div [formGroupName]="i">
<input formControlName="name" placeholder="Item name">
<input formControlName="description" placeholder="Item description">
<input formControlName="price" placeholder="Item price">
</div>
Chosen name: {{ orderForm.controls.items.controls[i].controls.name.value }}
</div>

Display input box on condition in angular reactive form

I am making angular 6 application where i am using angular reactive form.
Html:
<form [formGroup]="form">
<h2>Click the add button below</h2>
<button (click)="addCreds()">Add</button>
<div formArrayName="credentials" *ngFor="let creds of form.controls.credentials?.value; let i = index">
<div [formGroupName]="i" style="display: flex">
<select (ngModelChange)="changeAction($event)" formControlName="action">
<option *ngFor="let option of options" value="{{option.key}}"> {{option.value}} </option>
</select>
<input placeholder="Name" formControlName="name">
<div *ngIf="showLabel">
<input placeholder="Label" formControlName="label">
</div>
</div>
</div>
</form>
<pre>
{{ form ?.value | json }}
</pre>
Ts:
form: FormGroup;
showLabel: boolean = false;
options : any = [
{ "key" : "set", "value": "Set" },
{ "key" : "wait", "value": "Wait" },
{ "key" : "go", "value": "Go" }
]
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
credentials: this.fb.array([]),
});
}
addCreds() {
const creds = this.form.controls.credentials as FormArray;
creds.push(this.fb.group({
action: '',
name: '',
label: ''
}));
}
changeAction(e) {
if(e === "set" || e === "go") {
this.showLabel = true;
} else {
this.showLabel = false;
}
}
Working stackblitz: https://stackblitz.com/edit/angular-form-array-example-yksojj
In this given example there will be an add button, upon clicking that button you will get a select-box with values as set,wait,go and an input called name.. Upon click over add button the same row will be added and forms each object inside array.
Also you can see an if condition inside html for label,
<div *ngIf="showLabel">
<input placeholder="Label" formControlName="label">
</div>
The thing i am in the need was in the select box if i choose set or go then the label needs to displayed otherwise it should not be displayed for which i have written,
changeAction(e) {
if(e === "set" || e === "go") {
this.showLabel = true;
} else {
this.showLabel = false;
}
}
To be clear enough If the user clicks three times add button then the dropdown and name field alone needs to be displayed for three times whereas if the user selects the value from dropdown as set or go then the label input needs to be displayed to that particular row alone where the dropdown has the value set and go.. If the selection was wait then there should not be label box for the row which has dropdown value as wait.
Kindly help me to achieve the expected result..
If you add disabled:true property to formControl it will exclude fromControl from formGroup then you can enable formControl manually
creds.push(this.fb.group({
action: '',
name: '',
label: {disabled:true, value: ""}
}));
Then enable using enable method
changeAction(e,index) {
if(e === "set" || e === "go") {
this.showLabel = true;
this.form.get('credentials').at(index).get('label').enable();
} else {
this.showLabel = false;
this.form.get('credentials').at(index).get('label').disable();
}
}
Example:https://stackblitz.com/edit/angular-form-array-example-5buwyr
Please, NOT use (ngModelChange) or changeAction($event) to get the value of a control in an array -well, ngModelChange is for Template driven form, not for Reactive Form.
First change your form, create the form using a div with formArrayName="credentials", and a inner div *ngFor="let creds of form.get('credentials').controls
<!--a div with formArrayName--->
<div formArrayName="credentials" >
<!--a div *ngFor over "controls", in this div don't forget formGroupName-->
<div *ngFor="let creds of form.get('credentials').controls; let i = index"
[formGroupName]="i" style="display: flex">
<select formControlName="action">
<option *ngFor="let option of options" value="{{option.key}}">
{{option.value}}
</option>
</select>
<input placeholder="Name" formControlName="name">
<div *ngIf="??????????????">
<input placeholder="Label" formControlName="label">
</div>
</div>
</div>
Well, now the condition. To get the value of "action" you can use
form.get('credentials').at(i).value.action
//or, better
creds.value.action
So, your div becomes like
<div *ngIf="creds.value.action=='set' ||
creds.value.action=='go'">
<input placeholder="Label" formControlName="label">
</div>
This aproach avoid unnecesary code in your .ts.
this.showLabel
The scope of this variable is your whole component. Therefore, turning it on or off will show and hide all inputs.
You need a per-row value (creds.showLabel in your interface), or use this in your template :
*ngIf="['set','go'].includes(creds.action)"
Updated Stackblitz
By the way, this :
changeAction(e) {
if(e === "set" || e === "go") {
this.showLabel = true;
} else {
this.showLabel = false;
}
}
is more elegant written this way :
changeAction(e) {
this.showLabel = ['set','go'].includes(e)
}
or this way :
changeAction(e) {
this.showLabel = e in ['set','go']
}
Well, Jeremy's answer is pretty nice and enforces to use most of the native apis given by platform/language, however, here is a traditional approach to understand what is the actual flow and scope of objects, etc,etc...
Root Cause: The show hide field is made global to the scope of component, not at per formgroup level. So changing a single value, and using for all, will affect to all.
Solution:
Use Jeremy's answer for clean coding and less error prone code.
Manage an array of variable that will take care of sho/hide detail for each form group.
In the below answer, added some comments for easy understanding, and added console.log to see what exactly happening. Also, played with index i created in *ngFor and showed how you can make use of these things in future.
import { Component } from '#angular/core';
import { FormControl, FormGroup, FormArray, FormBuilder } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: 'app.component.html',
})
export class AppComponent {
form: FormGroup ;
showLabel = [];
creds : FormArray;
options : any = [
{ "key" : "set", "value": "Set" },
{ "key" : "wait", "value": "Wait" },
{ "key" : "go", "value": "Go" }
]
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
credentials: this.fb.array([]),
});
this.creds = this.form.controls.credentials as FormArray;
}
addCreds() {
this.creds.push(this.fb.group({
action: '',
name: '',
label: ''
}));
// every time adding new foem grp, adding lable show/hide data also.
this.showLabel.push(false);
}
changeAction(e, i) {
//every time input clikced, managing show/hide for that particular data also
console.log("iii", i, e.target.value);
if( e.target.value == "set" || e.target.value == "go") {
this.showLabel[i] = true;
} else {
this.showLabel[i] = false;
}
}
}
<form [formGroup]="form">
<h2>Click the add button below</h2>
<button (click)="addCreds()">Add</button>
<div formArrayName="credentials" *ngFor="let creds of form.controls.credentials?.value; let i = index">
<div [formGroupName]="i" >
<select (change)="changeAction($event, i)" formControlName="action">
<option *ngFor="let option of options" value="{{option.key}}"> {{option.value}} </option>
</select>
<input placeholder="Name" formControlName="name">
<div *ngIf="showLabel[i]">
<input placeholder="Label" formControlName="label">
</div>
</div>
</div>
</form>
<pre>
{{ form ?.value | json }}
</pre>
See live stackblitz working code
Note: Traditional is meant here.. as we every time do i.e. handle our problem on our own and create new problem on us to solve. It's not a tradition. :P

Disable Angular 5 Input fields correct way

I have a FormGroup that was created like that:
form: FormGroup;
constructor(private _formBuilder: FormBuilder) { }
this.form = this._formBuilder.group({
name: ['', Validators.required],
email: ['', Validators.required, Validators.email]
});
When an event occurs I want to disable those inputs, so, in the HTML I added:
<input class="form-control" placeholder="Name" name="name" formControlName="name" [(ngModel)]="name" autocomplete="off" [disabled]="isDisabled" required>
<input class="form-control" placeholder="Email" name="email" formControlName="email" [(ngModel)]="email" email="true" autocomplete="off" [disabled]="isDisabled" required>
Where isDisabled is a variable I toggle to true when the said event happens.
As you can imagine, I get the message:
It looks like you're using the disabled attribute with a reactive form
directive. If you set disabled to true
when you set up this control in your component class, the disabled attribute will actually be set in the DOM for
you. We recommend using this approach to avoid 'changed after checked' errors.
Example:
form = new FormGroup({
first: new FormControl({value: 'Nancy', disabled: true}, Validators.required),
last: new FormControl('Drew', Validators.required)
});
So, with the example this warning shows and with a little search I found that I should declare my controls like:
name: [{ value: '', disabled: this.isDisabled }, Validators.required]
The problem is: It is not toggling between disabled/enabled when the variable changes between true/false
How is the correct way of having a variable controlling if an input is enabled or disabled?
I don't want to do it manually (ex: this.form.controls['name'].disable()) because it doesn't seems a very reactive way, I would have to call it inside a good amount of methods. Probably not a good practice.
Thx
You can change the assignment of the variable to a setter method so that you'd have:
set isDisabled(value: boolean) {
this._isDisabled = value;
if(value) {
this.form.controls['name'].disable();
} else {
this.form.controls['name'].enable();
}
}
One solution is creating a directive and using binding for that as described in here
import { NgControl } from '#angular/forms';
#Directive({
selector: '[disableControl]'
})
export class DisableControlDirective {
#Input() set disableControl( condition : boolean ) {
const action = condition ? 'disable' : 'enable';
this.ngControl.control[action]();
}
constructor( private ngControl : NgControl ) {
}
}
then
<input class="form-control" placeholder="Name" name="name" formControlName="name" autocomplete="off" [disableControl]="isDisabled" required>
NOTE:
Doesn't work with Ivy
For input use [readonly] rather than [disabled] and it'll work
The proper way to disable an form control. With reactive forms you should never disable an input from the template. So in whatever method in your component you are calling you should disable the input like this:
this.form.get('name').disable();
Disable TextBox in Angular 7
<div class="center-content tp-spce-hdr">
<div class="container">
<div class="row mx-0 mt-4">
<div class="col-12" style="padding-right: 700px;" >
<div class="form-group">
<label>Email</label>
<input [disabled]="true" type="text" id="email" name="email"
[(ngModel)]="email" class="form-control">
</div>
</div>
</div>
</div>
You can use this code on your ts file.
All controls:
this.form.disable()
this.form.enable()
Some controls
this.form.get('first').disable()
this.form.get('first').enable()
Or Initial set method.
first: new FormControl({value: '', disabled: true}, Validators.required)
In Reactive Form you can disable all form fields by this.form.disable().
In Template Driven Form you can disable all form fields by this.myform.form.disable() where myForm is #ViewChild('form') myForm;
Not the clean or dry'st I imagine. Bu I tried the "set method" and didn't work out of the box...
Needed some refactoring () => {simpleVersion} (hope it helps someone)
component.ts
...
// standard stuff...
form: FormGroup;
isEditing = false;
...
// build the form...
buildForm() {
this.form = this.FormBuilder.group({
key: [{value:'locked', disabled: !this.isEditing}],
name: [],
item: [],
active: [false]
})
}
// map the controls to "this" object
// => i.e. now you can refer to the controls directly (ex. this.yourControlName)
get key() { return <FormControl>this.form.get('key') }
get name() { return <FormControl>this.form.get('name') }
...
// ----------------------------------------
// THE GRAND FINALÉ - disable entire form or individual controls
// ----------------------------------------
toggleEdit() {
if(!this.isEditing) {
this.key.enable(); // controls
this.name.enable();
// this.form.enable(); // the form
this.isEditing = !this.isEditing;
} else {
this.key.disable(); // the controls
this.name.disable(); // the controls
// this.form.disable(); // or the entire form
this.isEditing = !this.isEditing;
}
}
& perhaps overkill on the HTML logic, so hope you find the bonus integrated ngClass toggle just as helpful.
component.html (toggle button)
<div class="btn-group" (click)="toggleEdit()">
<label
class="btn"
role="button"
[ngClass]="{'btn-success': isEditing,
'btn-warning': !isEditing}">toggle edit
</label>
</div>
The solution by creating a directive and using binding for that worked for me in Angular 10 is described in here
Template:
<mat-form-field>
<input matInput class="form-control" formControlName="NameText" [disableControl]="condition" type="text">
</mat-form-field>
TypeScript:
import { Directive, Input } from '#angular/core';
import { NgControl } from '#angular/forms';
#Directive({
selector: '[opDisabled]'
})
export class DisabledDirective {
#Input()
set opDisabled(condition: boolean) {
const action = condition ? 'disable' : 'enable';
setTimeout(() => this.ngControl.control[action]());
}
constructor(private ngControl: NgControl) {}
}
I have a function that enables a control on click.
controlClick(control: any) {
this.form.controls[control.ngControl.name].enable();
}
Originally i was using
control.disabled = false;
But this did not work for controls with <input> for example in my mat-chip-list.
I use FormGroup and disable each control in the constructor
constructor(
private fb: FormBuilder,
private dialogRef: MatDialogRef<EditDialogComponent>,
#Inject(MAT_DIALOG_DATA) data
) {
this.data = data;
this.multiEdit = data.multiSelect;
this.form = new FormGroup({
autoArchive: new FormControl({
value:
this.getPreFill(data.selectedPolicy.autoArchive, this.multiEdit),
disabled: true
/*, Validators.required*/
}),
...
<mat-form-field (click)="controlClick(retrieveChipList)">
<mat-chip-list #retrieveChipList formControlName="retrieveChipList">
<mat-chip
*ngFor="let email of data.selectedPolicy.retrieveEmailsToBeNotified"
(removed)="remove(email)" [selectable]="selectable"
[removable]="removable"
>
{{ email }}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input
placeholder="Retrieve Emails to be Notified"
formControlName="retrieveChipList"
[matChipInputFor]="retrieveChipList"
[matChipInputAddOnBlur]="true"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="addRetrieveEmails($event)"
/>
</mat-chip-list>
</mat-form-field>
As control can't be accessed in reactive forms. This is due to migration to Ivy. You can use can access the html attribute directly and specify your condition. See this issue #35330 for more details and alternative methods.
[attr.disabled]="true || false"
You can create set and get method to achieve conditionally enable/disable functionality for Angular material Reactive Forms:
*// 1st Step:
set isDisabled(value:boolean) {
if(value){
this.form.controls['Form controller name'].disable(); //you can keep empty if you don't add controller name
}
else{
this.form.controls['Form controller name'].enable();
}
}
// 2nd Step: Add conditions in getter
get isDisabled(){
return condition ? true : false;
}
// 3rd Step
this.form = this._formBuilder.group({
name: [{value: '', disabled: this.isDisabled }, [Validators.required]],
});
Remove [disabled]="isDisabled" from input fields and add ng-disabled="all" and in the event field add ng-model="all"
<body ng-app="">
Click here to disable all the form fields:<input type="checkbox" ng-model="all">
<input class="form-control" placeholder="Name" name="name" formControlName="name" [(ngModel)]="name" autocomplete="off" ng-disabled="all" required>
<input class="form-control" placeholder="Email" name="email" formControlName="email" [(ngModel)]="email" email="true" autocomplete="off" ng-disabled="all" required>
</body>

Categories

Resources