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 placeholder="Name" name="name" formControlName="name" [(ngModel)]="name" autocomplete="off" [disabled]="isDisabled" required> <input 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
212 Answers
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(); } } 2One 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 placeholder="Name" name="name" formControlName="name" autocomplete="off" [disableControl]="isDisabled" required> NOTE:
Doesn't work with Ivy
3The 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(); 1Disable TextBox in Angular 7
<div> <div> <div> <div > <div> <label>Email</label> <input [disabled]="true" type="text" name="email" [(ngModel)]="email"> </div> </div> </div> </div> For input use [readonly] rather than [disabled] and it'll work
1You 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) 1In 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 (click)="toggleEdit()"> <label [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 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) {} } 1I 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]], });