51

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

João Ghignatti
  • 2,281
  • 1
  • 13
  • 25
  • I use the (change)="" in conjunction with [(ngModel)] works like a charms also check [this](https://stackoverflow.com/questions/44840735/change-vs-ngmodelchange-in-angular) SO post out pretty awesome IMHO – BSchnitzel May 07 '18 at 18:59
  • 4
    btw you don't need both `formControlName="name" [(ngModel)]="name" ` on your inputs – Reza May 07 '18 at 19:09

13 Answers13

36

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();
  }
}
Bruno Silva
  • 548
  • 3
  • 6
  • 2
    Why is this the correct answer? This looks a lot more like Kludge than a solution. – jwize Jan 11 '20 at 07:31
  • this is not a good approach, because the user can delete this attribute via the browser, in which case it will change in the data in the model? for example, I have a form where I saved user information, I did a add disabled attributes some fields, and I want to exclude the disabled one when saving the form. – Caner Dec 17 '21 at 17:13
15

For input use [readonly] rather than [disabled] and it'll work

Salim Hamidi
  • 20,731
  • 1
  • 26
  • 31
13

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

Reza
  • 18,865
  • 13
  • 88
  • 163
  • 1
    In Ivy (default since Angular 9) there's an error "Injected ngControl doesn't contain control property" (before Ivy it was ok). I found solution (although looking awkward) here: https://github.com/angular/angular/issues/35330 – jurekskowron Mar 13 '20 at 10:04
  • @jurekskowron thanks for heads up, it's worth to post you working example as answer – Reza Mar 13 '20 at 13:52
  • Thanks @jurekskowron. The mentioned solution worked for me in Angular 10. I have posted the same as a new answer. – iampranabroy Feb 10 '21 at 12:10
11

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();
Sandra Willford
  • 3,459
  • 11
  • 49
  • 96
  • I can still right-click the control, view source and remove the `disabled` attribute, making it enabled again. – Jnr Jan 30 '22 at 12:56
11

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>
nick
  • 425
  • 4
  • 12
5

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)
Piece
  • 708
  • 13
  • 15
  • 1
    For initial set method, it should be `first: new FormControl({value: '', disabled: true}, Validators.required)` as I tried. You have to specify the key(value) for the value. – Sky Aug 13 '20 at 03:29
3

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;

Aqeel Bahoo
  • 383
  • 2
  • 4
1

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>
Joe Code
  • 21
  • 4
1

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) {}
}
iampranabroy
  • 1,716
  • 1
  • 15
  • 11
  • 1
    I am using element.nativeElment in a Directive and using setTimeout solved my problem. But I don't know why setTimeout is helping in this case? – Gabor Feb 16 '21 at 21:33
1

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]],
  
});
0

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>
ldgorman
  • 1,553
  • 1
  • 14
  • 39
0

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"
Patrick Prakash
  • 500
  • 1
  • 7
  • 17
0

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>
Lishani
  • 57
  • 6