51

I'm using matInput and mat-form-field (@angular/material) in an Angular component, and I can't disable the matInput.

A working example can be seen here.

It seems likely that I'm missing something obvious, but for the life of me I can't figure out what. Is this a bug?

If I remove [formControlName] from the CustomFormInputComponent, then I can successfully disable the matInput

CustomFormInputComponent:

import { Input, Component } from '@angular/core';
import { FormGroup } from '@angular/forms';

@Component({
  selector: 'app-custom-form-input',
  template: `
    <mat-form-field [formGroup]="form">
      <input matInput placeholder='Name' [formControlName]="formControlName" [disabled]='disabled'>
    </mat-form-field>
  `,
})
export class CustomFormInputComponent  {
  @Input() form: FormGroup;
  @Input() formControlName: string = 'name';
  @Input() disabled = false;
}

AppComponent:

import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';

@Component({
  selector: 'my-app',
  template: `
    <p>At least one of these inputs should be disabled, but none are :(</p>

    <app-custom-form-input [form]="form" [disabled]='true'></app-custom-form-input>

    <app-custom-form-input [form]="form" [disabled]="'disabled'"></app-custom-form-input>
  `,
})
export class AppComponent  {
  public form: any;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.form = this.fb.group({
      name: ''
    })
  }
}

Any insights are greatly appreciated!

Answer

For a bit more context on David's answer: Angular updates DOM state based on the disabled status of a reactive form control. What I think is happening: angular is rendering the CustomFormInputComponent before the AppComponent and is rendering the component as disabled. Then the AppComponent is rendered and the form is built with the name control enabled. Angular then goes and un-disabled the DOM input element (which is behavior as designed).

Stefan Falk
  • 23,898
  • 50
  • 191
  • 378
John
  • 9,249
  • 5
  • 44
  • 76

10 Answers10

72

If you are using a FormGroup, then you should not disable the form in the HTML Template. It will not work if you try to disable in HTML together with FormControl. Instead, it should be done within the FormGroup. Try this:

  template: `
    <mat-form-field [formGroup]="form">
      <input matInput placeholder='Name' [formControlName]="formControlName">
    </mat-form-field>
  `

and:

ngOnInit() {
    this.form = this.fb.group({
        name: new FormControl({ value: '', disabled: this.disabled })
    });
}

Also...not a big deal but..you should really be doing:

public form: FormGroup;

instead of:

public form: any;

Don't forget the import as well

import { FormGroup, FormControl } from '@angular/forms';

Btw, the name inside of the form group declaration should match whatever you have set in the HTML. So:

<input formControlName="myInputName">

and

this.form = this.fb.group({
    myInputName....
});
Stefan Falk
  • 23,898
  • 50
  • 191
  • 378
David Anthony Acosta
  • 4,766
  • 1
  • 19
  • 19
  • 2
    On what are you basing your assertion that _"If you are using a FormGroup, then you should not disable the form in the HTML Template"_? No where in the [reactive form guide](https://angular.io/guide/reactive-forms) is this stated, so far as I can tell. What makes you think the behavior I'm experiencing isn't a bug? – John Jan 25 '18 at 20:56
  • For example, multiple `input` elements can bind to the same form control (I think). It's not immediately obvious to me why I couldn't disable one of those inputs in the html, but leave the reactive form control enabled so that the other inputs binding to it still worked. – John Jan 25 '18 at 20:59
  • Because in that same guide, you can clearly see that all the validation on reactive forms is taking place in the class, not in the HTML. Even something as simple as "required" is added in the component class in their examples. No where in their guide is it done in the HTML.... Otherwise why even use reactive forms – David Anthony Acosta Jan 25 '18 at 20:59
  • I think you are a bit confused about how FormControl, FormGroup work. – David Anthony Acosta Jan 25 '18 at 21:02
  • Also, it's not even an assertion. Angular has a built in console warning that basically says "HEY DON'T DO THAT, DO IT IN THE CLASS". Open your console when you attempt to disable an input with formControlName and you will see it. It's pretty clear it's not meant to be disabled in that way. – David Anthony Acosta Jan 25 '18 at 21:04
  • Well A) I hadn't noticed that. Thanks! B) "We recommend using this approach to avoid 'changed after checked' errors." Clearly disabling via the DOM is supported, otherwise they would say "you _must_". Not _recommend_. But this does seem to explain what's happening. Angular is (probably) rendering the input element as disabled, and then enabling it after that when it builds the parent element. – John Jan 25 '18 at 21:08
  • 1
    No, multiple elements cannot share the same FormControl. Recommended basically means must.For example, it's recommended you encrypt passwords. But it's not a must. Would you then not encrypt them and just say "Well it's recommended, it's not a must...." – David Anthony Acosta Jan 25 '18 at 21:13
  • @DavidAnthonyAcosta But if I change this.disabled value to false - my control stays disabled. Is there a way to undisable the control? – Tatyana Sep 07 '18 at 08:55
  • 7
    @Tatyana - You can enable/disable form controls programmatically e.g. `this.printForm.controls['title'].enable();` or `this.printForm.controls['title'].disable()` – DeadPassive Dec 18 '18 at 15:49
  • Hi, I know this is correct, but I have a different case, I want to conditionally disable form controls without losing their values. Example: I am loading some form data and manually filling some fields and I want these fields to become disabled, how do I do that without losing the value from FormGroup? Note that "this.myForm.get('formcontrol').disable();" will remove the value from FormGroup and I don't want it to be disabled initially unless I have a specific case. – Mohamad Al Asmar Oct 05 '20 at 14:20
  • thanks a lot .I was unable to disable input using attribute . working fine by disabling using the form control name .this.form.get('fcn').disable() . – Aditya Yada Mar 09 '21 at 15:43
40

 <mat-form-field fxFlex>    
   <input matInput placeholder="No" formControlName="no" readonly>    
 </mat-form-field>

Have you tried readonly option. This works fine with me.

Working code: html:

 <mat-form-field fxFlex>    
   <input matInput placeholder="No" formControlName="no" readonly>    
 </mat-form-field>

In case you need to bind it to a disabled property of your component

<input matInput [readonly]="disabled" (keyup)="search()"
    [formControl]="..." class="...">
Mauricio Gracia Gutierrez
  • 10,288
  • 6
  • 68
  • 99
yogesh chavan
  • 599
  • 5
  • 16
  • 2
    did not know about 'readonly' property, worked for me. – Coder0997 Jan 18 '21 at 16:54
  • 1
    Yogesh thank you. This was a one word fix for code I was already implementing. Much appreciated! – Willie Jun 22 '21 at 16:48
  • 2
    It is downvoted because "readonly" is not the same as "disabled". An input component that is disabled does not react to any input, whereas with "readonly" the input does react but you cannot change the content... – Manuel Astudillo Nov 02 '22 at 14:52
9

I have had the same issue and I have solved it with *ngIf directive. If input should be disabled, make it disabled, remove its form binding and give its value manually. If it's not, than use your formControl as it is.

This is your template:

<mat-form-field [formGroup]="form">
  <input matInput placeholder='Name' [formControlName]="formControlName" [disabled]='disabled'>
</mat-form-field>

change it with:

<mat-form-field *ngIf="disabled">
  <input matInput placeholder='Name' [value]="form.formControlName" disabled='true'>
</mat-form-field>
<mat-form-field *ngIf="!disabled" [formGroup]="form">
  <input matInput placeholder='Name' [formControlName]="formControlName">
</mat-form-field>
boyukbas
  • 1,137
  • 16
  • 24
8

-->output try this.

.html file

   <form name="fg" [formGroup]="fg" >
        <mat-form-field >
                  <input matInput placeholder="Email" formControlName="email">
           </mat-form-field>
    </form>

.ts file import this : import { FormBuilder, FormGroup, Validators } from '@angular/forms';

constructor(private _formBuilder: FormBuilder) { }



   this.fg= this._formBuilder.group({
        email :[
            {
              value : 'vijay@gmail.com',
              disabled: true
            },
Validators.required
        ],
Vijay Prajapati
  • 738
  • 1
  • 7
  • 20
6

If you are using FormGroup you have to use disabled property creating your FormGroup/FormControl:

name: new FormControl({ value: '', disabled: this.disabled })

But if you want to disable/enable you can use this in your HTML:

<input type="text" formControlName="name" [attr.disabled]="isDisabled == true ? true : null" />
Saad Abbasi
  • 745
  • 11
  • 25
juanjinario
  • 596
  • 1
  • 9
  • 24
2

I hade the same issue and already know the other options. But all of them had at least one issue:

1)

<input [formControl]="form" readonly>
// not working with binding. You have to decide it once at first initiating
this.form.disable();
// I have to subscribe on variable changes to trigger enable/disable method
new FormControl({ value: '', disabled: this.disable });
// Again, it is not enable/disable on variable change
<mat-form-field *ngIf="disabled">...</mat-form-field>
<mat-form-field *ngIf="!disabled">...</mat-form-field>
// It is working! But on common changes these doms will everytime fully initialized

So I decided to use the following solution:

<mat-form-field [class.disabled]="disable">...</mat-form-field>

<style>
    .disabled {
        pointer-events: none;
        color: rgba(0, 0, 0, 0.38);
    }
</style>

By this the user is not able to interact with the input anymore. And the style is the same like on disabled.

Of course someone can remove the class by the developer bar, but this is also possible with disabled attribute. So in any case, you should also protect the JavaScript logic behind the input.

Nico Schuck
  • 832
  • 3
  • 15
  • 32
  • Same for me. Disabling the form control messes up the value gathering big time. Especially when using valueChanges. I tried a bunch of stuffs, but in my case only your css solution worked for me. Thanks for sharing! – A. Parolini Mar 10 '23 at 16:29
1

Everyone reading this needs to be aware of the following:

  1. Disable() method will remove the value from your from. Debug your form Group, and look at what happens when you use .disable() on an input. You will have a FormControl still, but the value for that particular input will be gone. Look at FormGroup.value when you use enable()/disable().

  2. The readonly answer above is by far the easiest way to implement disabling an input that you still need a value from

  3. Using a combination of readonly and ngIf/ng-templates you will be able to successfully toggle a disabled input and still capture it's value in your form.

  4. Disabling a reactive form in the DOM simply doesn't work like you think it should.

  5. When you disable an input during the FormControl creation, it doesn't matter if you bind the disabled property to a variable that you are going to try and manipulate later. It only takes a snapshot of that properties value at the time of creation and sets disabled as that boolean value on the control. Try and manipulate that value all you want, the control has already been created and doesn't care what the new value is.

An example using the readonly answer above, and ng-templates:

<div class="col-4">
  <div *ngIf="hasVarHeight.checked else normalHeight">
    <mat-form-field>
      <mat-label>Height</mat-label>
      <input
        type="text"
        matInput
        [formControl]="heightCtrl"
        [value]="'*'" readonly>
    </mat-form-field>
  </div>
  <ng-template #normalHeight>
    <mat-form-field>
      <mat-label>Height</mat-label>
      <input
        type="text"
        matInput
        [formControl]="heightCtrl"
        mask="separator.3"
        thousandSeparator=","
        [suffix]="unitCtrl.value === 'Metric' ? ' mm' : ' in'"
        [placeholder]="unitCtrl.value === 'Metric' ? 'mm' : 'inches'"
        [dropSpecialCharacters]="false">
      <mat-error *ngIf="heightCtrl.hasError('required')">
        Height is required
      </mat-error>
    </mat-form-field>
  </ng-template>
</div>
</div>
<div class="form-row">
<div class="col-4">
  <mat-checkbox
    #hasVarHeight
    (click)="makeVariable('height')"
    name="varheight"
    [formControl]="hasVarHeightCtrl">
    Variable Height?
  </mat-checkbox>
</div>
</div>

Hopefully the above gives you an idea about how you can use readonly and templates to make your form better, and still retain masks on inputs that you require. You can use a method like makeVariable() above to dynamically set a value on a readonly input, and present the user with something useful as well.

Just a thought. Thank you for the readonly answer Yogesh.

Willie
  • 281
  • 3
  • 21
0

If You are using material then you can add

<mat-form-field [formGroup]="form">
  <input matInput placeholder='Name' [formControlName]="formControlName" readOnly>
</mat-form-field>
N_B
  • 17
  • 2
0

While I agree that using readonly is the easiest way forward, it's often not clear to a user that the read only element is "off-limits". If you truly want the "normal" disabled look/behavior on the HTML form, but still want/need the value to be included in the form submission, include a hidden form input that Angular will control, along with the visible and disabled input that displays a copy of the hidden value...best of both worlds? This seemed easier than using read only but trying to style the control to look disabled.

In this example, I want the internal id to display and clearly be disabled, but still need it included in the form submission.

<input type="hidden" formControlName="id">
<input disabled="" [value]="theId">

The input will look and behave like a disabled control, but any submission will include the hidden value.

Paul
  • 11
  • 4
-2

In the html page:

<form [formGroup]="formForAddDSBData">
              <mat-form-field class="form-element"  >
              <mat-icon matPrefix><i class="fa fa-address-card fa-1x"></i></mat-icon>
              <input matInput disabled id="AreaAddress" formControlName="AreaAddress" 
              #Contact1 placeholder="Area/Address" type="text" >
               <mat-error *ngIf="!formForAddDSBData.controls['AreaAddress'].valid && 
               formForAddDSBData.controls['AreaAddress'].touched">
                Area/Address is required
              </mat-error> 
      </mat-form-field>
</form>

to disable this mat-input you need to write the below code in .ts file

this.formForAddDSBData.get("AreaAddress").disable();
Maximilian Ast
  • 3,369
  • 12
  • 36
  • 47