57

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

2
  • I use the (change)="" in conjunction with [(ngModel)] works like a charms also check this SO post out pretty awesome IMHO Commented May 7, 2018 at 18:59
  • 4
    btw you don't need both formControlName="name" [(ngModel)]="name" on your inputs Commented May 7, 2018 at 19:09

13 Answers 13

42

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();
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Why is this the correct answer? This looks a lot more like Kludge than a solution.
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.
25

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

2 Comments

Yes, it will work, but attributes readonly and disabled, have really different meaning.
For anyone curious: "The difference between disabled and readonly is that read-only controls can still function and are still focusable, whereas disabled controls can not receive focus and are not submitted with the form and generally do not function as controls until they are enabled." - developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly And it also changes the cursor. Use cursor: default; and tabindex="-1" to make it more similar to the disabled state.
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

3 Comments

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: github.com/angular/angular/issues/35330
@jurekskowron thanks for heads up, it's worth to post you working example as answer
Thanks @jurekskowron. The mentioned solution worked for me in Angular 10. I have posted the same as a new answer.
12

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();

1 Comment

I can still right-click the control, view source and remove the disabled attribute, making it enabled again.
12

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>

Comments

6

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)

1 Comment

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.
4

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;

Comments

3

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"

Comments

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>

Comments

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) {}
}

1 Comment

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

Comments

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>

Comments

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.