components: MdInput doesn't set required asterisk when the validator is set in reactive forms

Bug, feature request, or proposal:

The placeholder from the MdInput does not show the asterisk (*) when we use Validators.required in the FormControl.

What is the expected behavior?

The asterisk should be shown as it is when setting the requiredattribute.

What are the steps to reproduce?

http://plnkr.co/edit/XqkOmF502Q8RFj8qcUwp?p=preview or take a look at the input-container-demo

What is the use-case or motivation for changing an existing behavior?

To be not dependet on the template to visually indicate that the input is required.

Which versions of Angular, Material, OS, browsers are affected?

all (to my knowlege it never worked)

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 114
  • Comments: 59 (10 by maintainers)

Commits related to this issue

Most upvoted comments

I’m using this solution:

<input formControlName="name" [required]="formGroup.get('name').errors !== null && formGroup.get('name').errors.required">

Any ETA ??

I implemented a Monkey Patch for this for now, based on the proposed PR from Alexandre (https://github.com/angular/material2/pull/14795)

// ADD THIS MODULE IN YOUR PROJECT, AND LOAD IT IN THE MAIN CLASS
import {MatInput} from '@angular/material';
import {coerceBooleanProperty} from '@angular/cdk/coercion';

/**
 * Fix for the MatInput required asterisk.
 */
Object.defineProperty(MatInput.prototype, 'required', {
	get: function (): boolean {
		if (this._required) {
			return this._required;
		}

		// The required attribute is set
		// when the control return an error from validation with an empty value
		if (this.ngControl && this.ngControl.control && this.ngControl.control.validator) {
			const emptyValueControl = Object.assign({}, this.ngControl.control);
			(emptyValueControl as any).value = null;
			return 'required' in (this.ngControl.control.validator(emptyValueControl) || {});
		}
		return false;
	},
	set: function (value: boolean) {
		this._required = coerceBooleanProperty(value);
	}
});

Any intent to fix this? Or should we settle with hacks/patches provided in this thread? 😃

Now that https://github.com/angular/angular/pull/42838 has landed we should be able to implement this

I needed more functional directive than applying a class to required to mat-form-field. I was needing to display the asterisk from reactive form validator to prevent duplicate management (and conflicts) by template + reactive form.

The directive matches only MatFormFields that have MatInput or MatSelect that don’t have [required] attribute. When elligible, the required status (determined as described by @Khaldor48) updates the MatFormControl.required status, updating the template and the showing/hiding the required asterisk.

Usage

<mat-form-field>
   <input matInput [formControl]="myControl">
</mat-form-field>
myControl = new FormControl('', Validators.required);

Directive

/**
 * Input/Select into FormField consider Validator.required from reactive form if the [required] attribute is missing in the template
 */
@Directive({
    selector: 'mat-form-field:has(input:not([required])), mat-form-field:has(mat-select:not([required]))',
})
export class ReactiveAsteriskDirective implements AfterContentChecked {
    constructor(private matFormField: MatFormField) {}

    ngAfterContentChecked() {
        const ctrl = this.matFormField._control;
        if (ctrl instanceof MatInput || ctrl instanceof MatSelect) {
            ctrl.required = ctrl.ngControl?.control?.validator?.({} as AbstractControl)?.required;
        }
    }
}

hey guys, is there any update on this?

Simply add <input … [required]=“true” />

@ajaysattikar That would be template-driven forms. This whole discussion is about reactive forms.

well… no pretty solution… but you can add an additional directive to force the asterisk: https://stackblitz.com/edit/angular-material2-issue-qdqb1n?file=app%2Fmat-form-field-required.directive.ts

Below solution works for me:

<input formControlName="name" [required]="formGroup.get('name').validator('')['required']"> 

it doesn’t solve anything except giving us an easy shorthand to manually add the star on reactive forms

@donacross But we don’t need this “workaround”. Manually adding required to reactive form is already adding the asterisk. The entire point of this thread is that it should also be added when the proper validator is set.

Any update on this?

Knowing if the required validation was added or not doesn’t seems like something that core angular is going to support, because validators are just functions. It is even possible to write a custom validation function that conditionally checks required, such as if (something) return Validators.required(control) else null. So, I would like to suggest adding a new attribute such as matRequired, which just visually add the asterisk, but doesn’t touch the validators. To be honest, this is the only solution I can think on this case, given that angular is unlike to support the requirement. This was proposed in #4090, but it was closed as a dupe of the current issue, but they are, in fact, different things. I’m suggesting in this one because it is the currently open issue.

@daankets Thanks. Your Monkey Patch still works in Angular 9 and is also applicable to MatSelect: https://stackblitz.com/edit/angular9-reactive-form-required

import { Directive } from '@angular/core';
import { MatFormField } from '@angular/material/form-field';
import { MatInput } from '@angular/material/input';
import { AbstractControl } from '@angular/forms';

@Directive({
  selector: 'mat-form-field[provideRequiredClass]',
  host: {
    '[class.mat-form-field-required]': 'isRequired()'
  }
})
export class MatFormFieldCustomDirective {
  constructor(private _matFormField: MatFormField) {
  }

  public isRequired(): boolean {
    const ctrl = this._matFormField._control;
    if (ctrl instanceof MatInput) {
      const validators = ctrl.ngControl.control.validator?.({} as AbstractControl);
      return validators?.required;
    }
    return false;
  }

}

I spent many hours to find a working non-all-performance-consuming solution. This is my solution (which is composed from other solutions found here and on stackoverflow). So thank you guys for helping me with this.

Note: I had implemented conditional required fields, which are not affected by this solution. If I found something useful I will post it here afterwards.

Note2: It requires typescript >= 3.7.0 because of optional chaining, but this can be easily workarounded with

const validators = ctrl.ngControl.control.validator && ctrl.ngControl.control.validator({} as AbstractControl);
return validators && validators.required;

Just a question: This one has been open for 2 years already… Is there still an intent to fix this one?

@bboehm86 that’s a great solution, but there’s a small bug that causes an error if validator(new FormControl()) is null, which is the case when a control only has validators that are valid.

This stackblitz shows the error occurring where the validators are valid (i.e. return null). Check the console to see them.

The fix is to check if running the validator on an empty FormControl returns not null. The fix is in this stackblitz. Here’s the diff:

27c27,28
<           this._control.required = validator(new FormControl()).required;
---
>           const vf = validator(new FormControl());
>           this._control.required = vf ? vf.required : null;

Unfortunately FelipeCarriel solution is not related to this issue. This issue is specifically about reactive form, but that solution is for template driven form.

Monkey patching still seems to be the best workaround available.

@isherwood Yes, sorry. Edited to avoid confusion.

Yeah but the underlying problem seems to be the same, only checking the existence of the required input, instead of checking whether required validator exists(which I think will be a little tricky).

For future reference, https://github.com/angular/angular/issues/22422 is the issue blocking this one.

well… no pretty solution… but you can add an additional directive to force the asterisk: https://stackblitz.com/edit/angular-material2-issue-qdqb1n?file=app%2Fmat-form-field-required.directive.ts

This solution proposed by @bboehm86 works for me, adding the fix that @kwkelly commented bellow, but I found other bug in lines 21, 22, because for access the first case you always need that this._control.ngControl['form'] exists, so we need to remove the this._control.ngControl.validator condition

21c21
<           this._control.ngControl.validator || this._control.ngControl['form'] // [formControl] standalone
---
>           this._control.ngControl['form'] // [formControl] standalone

For me, it’s a great solution while we wait for an official solution

Any solution for this?

Is there any update on this?

Using the required attribute doesn’t work well if the input is inside an ngIf I have found. The core forms automatically adds the required validator to the control when the required attribute is used on the input but it doesn’t go away once the input is no longer in the dom. Maybe that’s a separate issue for the core side not cleaning up when an element is removed from the dom. This leads to a form appearing invalid when it shouldn’t be.