components icon indicating copy to clipboard operation
components copied to clipboard

bug(MatFormFieldControl): Custom MatFormFieldControl does not shows errors on form submit unless touched

Open marekwalach opened this issue 3 years ago • 7 comments

Reproduction

Steps to reproduce:

  1. Follow documentation https://material.angular.io/guide/creating-a-custom-form-field-control to create custom MatFormFieldControl with ControlValueAccessor
  2. Use it in Reactive Form (with some other simple fields)
  3. Try to save form without touching custom field.

StackBlitz (forked from documentation example with added form element and submit button): stackblitz

Expected Behavior

Form is invalid from the start and on submit all custom MatFormFieldControls are marked as invalid even if not touched.

Actual Behavior

When submitting form all standard fields will be marked as invalid (red) but custom one will not. If you write something into this custom field and then remove this value (making it dirty and touched) then it will correctly be shown as invalid. What is more whole form is VALID if this field is not touched - after you it is dirty then whole form is correctly INVALID.

Environment

  • Angular: 12.2.1
  • CDK/Material: 12.2.1
  • Browser(s): all
  • Operating System (e.g. Windows, macOS, Ubuntu): all

marekwalach avatar Aug 17 '21 08:08 marekwalach

Your error state matchers are different.

What you have is

get errorState(): boolean {
  return this.ngControl.errors !== null && !!this.ngControl.touched;
}

But Material will check for submitted as well

isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
  return !!(control && control.invalid && (control.touched || (form && form.submitted)));
}

I assume that guide doesn't have .submitted as well for simplicity.

BojanKogoj avatar Aug 18 '21 20:08 BojanKogoj

@BojanKogoj I updated SB with your code but it changes nothing.

marekwalach avatar Aug 19 '21 06:08 marekwalach

Your CustomStateMatcher isn't being used. Material uses mixinErrorState which calls isErrorState.

I modified your example , changed errorState

  get errorState(): boolean {
    const parent = this.parentForm || this.parentFormGroup;
    return !!this.parts.invalid && (!!this.parts.touched || parent.submitted);
  }

And added injected FormGroup and NgForm.

    @Optional() private parentForm: NgForm,
    @Optional() private parentFormGroup: FormGroupDirective

BojanKogoj avatar Aug 20 '21 14:08 BojanKogoj

@BojanKogoj that work with error state which "marks field red". But whole form is still VALID without writing anything into this field.

marekwalach avatar Aug 23 '21 06:08 marekwalach

Same as https://github.com/angular/components/issues/23352 Not sure how to solve this right now.

BojanKogoj avatar Aug 23 '21 20:08 BojanKogoj

@marekwalach I'm missing something, maybe it's an emit of the value when the writeValue is started, I believe that implementing ngAfterViewInit with an onChange would solve this problem.

ngAfterViewInit(): void { this.onChange(this.value); }

Can you confirm if it works?

viniciusschuelter avatar Sep 12 '22 23:09 viniciusschuelter

@viniciusschuelter it changes nothing unfortunately.

marekwalach avatar Sep 13 '22 06:09 marekwalach

There are some error triggers that we can't subscribe to (e.g. parent form submissions). so you just have to check for error state update on DoCheck. reference: https://github.com/angular/components/blob/main/src/material/input/input.ts#L350

I will make a PR for it to make the docs more clear.

behzadmehrabi avatar Apr 27 '23 22:04 behzadmehrabi

This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.