The placeholder
from the MdInput does not show the asterisk (*) when we use Validators.required
in the FormControl
.
The asterisk should be shown as it is when setting the required
attribute.
http://plnkr.co/edit/XqkOmF502Q8RFj8qcUwp?p=preview
or take a look at the input-container-demo
To be not dependet on the template to visually indicate that the input is required.
all (to my knowlege it never worked)
Likely the same problem as the one that this PR fixes https://github.com/angular/material2/pull/2565
Probably not exactly the same because the control does not have a required
state since it is a validator (other than in the disabled
case)
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).
@kara
While you are at it... the md-select
seems to have the same issue (plnkr). Should i open another ticket for that?
Currently there isn't a great way to determine if Validators.required has been added to the control because form validators are combined into an aggregate function at setup. I think the correct solution to this might be to add a better way to retrieve individual validators in core @angular/forms
.
In the meantime, you'll have to use the required property binding when using md-select or md-input with reactive forms. You'll probably want to use it with the Validators.required to avoid changed after checked errors.
I'll keep this open so we can track, but I think the change will have to be in core first.
@kara thanks for the info. I guess we'll have to wait then :-)
..and Kara is right, there seems not to be a great way to get a hold of the Validators.required
(or any other validator).. Actually I only found one, but for anyone who rly needs to get a hold of its existance of the required you can do smth like:
myControl = new FormControl('', Validators.required);
validationResult = myControl.validator(new FormControl); // will be {required: true}
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.
I'm using this solution:
<input formControlName="name" [required]="formGroup.get('name').errors !== null && formGroup.get('name').errors.required">
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.
Simply add
@ajaysattikar That would be template-driven forms. This whole discussion is about reactive forms.
@isherwood Yes, sorry. Edited to avoid confusion.
@eltonplima except that it disappears as soon as the form is valid, which means that it would cause problems with for example the outline appearance, along with missing validChanges
on form controls this is nothing that can be easily dealt with.. cc @mmalerba
edit: nevermind, you can use status changes with filter to get validity change
edit2: ok, that's not gonna work, what I would need is an observable array of errors / validity states......... which of course you can't get.... https://github.com/angular/angular/issues/10530 ffs...
Any ETA ??
Is there any update on this?
hey guys, is there any update on this?
Any solution for this?
@donacross I don't quite understand how this solves the originally mentioned problem. You still set required attribute on the Validators.required
in the FormControl
.
Or am I missing something?
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.
@ngehlert Sincere apologies, I was way too blinded by my use case. I've been able to reproduce my error and fix it, so you may see what I was referring too. I deleted my previous comments to avoid confusion, I hope you don't mind. Thanks for your time and feedback, really appreciate it.
Any update on this?
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
@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;
Below solution works for me:
<input formControlName="name" [required]="formGroup.get('name').validator('')['required']">
this.form = this.formBuilder.group({
variableOne: new FormControl(this.variableOne.name, Validators.required),
variableTwo: new FormControl(this.variableTwo, Validators.required)
})
I have code like this, like about hundreds of form controls around my project, and I was looking for an automated solution to add an asterik to all of them instead of going around all the html files and adding required attributed to all of them.
so the actual problem lies (I think) is when we specify Validators.required, it should behave exactly the same as adding a required attribute on the html element(which does add an asterik)
Is there any ETA on this?
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 update on this?
Just a question: This one has been open for 2 years already... Is there still an intent to fix this one?
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);
}
});
daankets monkey patch works. Why is something similar not yet in production?
Edit:
I added a debugger in get and set. The get function run 8+ times. For a single simple input field? Is this an performance leak when we consider having 100+ form-fields?
When I click again and CD runs it gets triggered again that often :/.
It would be great if you could have a mat-form-field function - "updateRequiredState()" so we could only trigger it once when needed.
@daankets thank you!
This SO question describe how to know if the required validator has been used
export const hasRequiredField = (abstractControl: AbstractControl): boolean => {
if (abstractControl.validator) {
const validator = abstractControl.validator({}as AbstractControl);
if (validator && validator.required) {
return true;
}
}
if (abstractControl['controls']) {
for (const controlName in abstractControl['controls']) {
if (abstractControl['controls'][controlName]) {
if (hasRequiredField(abstractControl['controls'][controlName])) {
return true;
}
}
}
}
return false;
};
Any intent to fix this? Or should we settle with hacks/patches provided in this thread? :)
@daankets Thanks. Your Monkey Patch still works in Angular 9 and is also applicable to MatSelect:
https://stackblitz.com/edit/angular9-reactive-form-required
in Angular, using Angular Material, I solved this problem by removing the placeholder attribute from the input tag and adding a mat-label tag to the form-field.
before:
<mat-form-field appearance="outline">
<input matInput
#nameInput
type="text"
formControlName="name"
(keyup.enter)="validate()"
placeholder="Name"
required>
</mat-form-field>
now:
<mat-form-field appearance="outline">
<mat-label>Name</mat-label>
<input matInput
#nameInput
type="text"
formControlName="name"
(keyup.enter)="validate()"
required>
</mat-form-field>
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.
For future reference, https://github.com/angular/angular/issues/22422 is the issue blocking this one.
@daankets thanks!
That a great solution for now.
Like @andreElrico had said, the function is called couple of times, but it must have angular performance.
Important point is it could cause an null error in custom validator, so null must be checked.
I need to show the * before the user interacts with the form so this is my workaround: [required]="formControl?.validator?.name === 'required'" [formControl]="formControl"
I think you could wrap this in a directive
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;
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.
<mat-form-field>
<input matInput [formControl]="myControl">
</mat-form-field>
myControl = new FormControl('', Validators.required);
~~~ts
/**
ngAfterContentChecked() {
const ctrl = this.matFormField._control;
if (ctrl instanceof MatInput || ctrl instanceof MatSelect) {
ctrl.required = ctrl.ngControl?.control?.validator?.({} as AbstractControl)?.required;
}
}
}
~~~
Hi All, I'm experiencing the same issue using Control Value Accessor with a material autocomplete, for now the only solution is to apply the above directive mentioned by @sambaptista ?
Hi All, I'm experiencing the same issue using Control Value Accessor with a material autocomplete, for now the only solution is to apply the above directive mentioned by @sambaptista ?
Yes
I think my issue is a bit different... Material/ReactiveForm is completely ignoring the required validation when I'm using the CVA so also I'm passing errorStateMatcher but still not work... The input is not getting red borders and neither the asterisk, it's like is a not required input.. any suggestion to deal with that?
@BruneXX have you checked so that your control is correct? does {{ yourControl.errors }} contain an object?
Hi @mackelito yep, it already has an object and it's correct, but I've already solved that, subscribing it to statusChanges of the parent form, the sad thing is that I've to pass the parent fromGroup in order to subscribe, unfortunately I haven't found other way to make it work.
This is exactly what I hope to avoid 馃槵
@mackelito haha me too, but sadly I don't have too much time to review that.. I had to go with that approach until found another way.
@BruneXX I have been trying on and off for like 2 days now.. not sure I can put more time on it 馃槃
@BruneXX Wohoo.. I just solved my issue.. almost to simple.. I just needed to add the [formControl]...
[formControl]="ngControl"
constructor(
@Optional() @Self() public ngControl: NgControl,
) {
if (this.ngControl != null) {
// Setting the value accessor directly (instead of using the providers) to avoid running into a circular import.
this.ngControl.valueAccessor = this;
}
}
So only thing missing was to attach the formcontrol to the input.. Badabom badabing! :D
Hope it helps you as well..
@mackelito Excellent! :+1: I've tried something like that some days ago, but I've ran into the circular dependency like you said in your code comment and I've leaved as it was, maybe I'll give this a second try later. Thank you!
@BruneXX sounds like you are using the provider for NG_VALUE_ACCESSOR?.. if so.. remove it :)
FYI.. it works but another error in the console...
ERROR TypeError: Cannot set property validator of [object Object] which has only a getter
Not sure yet what it referes to :)
Hi, this is my long used solution, It handles validator changes too.
@Directive({ selector: '[matInput][formControl]:not([required]), [matInput][formControlName]:not([required])' })
export class InnoRequiredMatInput implements DoCheck {
constructor(private readonly input: MatInput) { }
ngDoCheck() {
const isRequired = (this.input.ngControl?.control as any)?.isRequired ?? false;
if(isRequired !== this.input.required){
this.input.required = isRequired;
this.input.ngOnChanges();
}
}
}
@Directive({ selector: 'mat-select[formControl]:not([required]), mat-select[formControlName]:not([required])' })
export class InnoRequiredMatSelect implements DoCheck {
constructor(private readonly input: MatSelect) { }
ngDoCheck() {
const isRequired = (this.input.ngControl?.control as any)?.isRequired ?? false;
if(isRequired !== this.input.required){
this.input.required = isRequired;
}
}
}
const _clearValidators = AbstractControl.prototype.clearValidators;
AbstractControl.prototype.clearValidators = function(){
(this as any).isRequired = false;
_clearValidators.call(this);
}
const _setValidators = AbstractControl.prototype.setValidators;
AbstractControl.prototype.setValidators = function(newValidator: ValidatorFn | ValidatorFn[] | null): void {
(this as any).isRequired = false;
_setValidators.call(this, newValidator);
}
Validators.required
insideexport class MatValidators{
static required(control: AbstractControl): ValidationErrors | null{
(control as any).isRequired = true;
return Validators.required(control);
}
}
Simply add
@ajaysattikar That would be template-driven forms. This whole discussion is about reactive forms.
This solution worked perfectly along with formControlName. Thanks!
Most helpful comment
I'm using this solution:
<input formControlName="name" [required]="formGroup.get('name').errors !== null && formGroup.get('name').errors.required">