4

I've tried to build an angular component to wrap a whole mat-form-field of Angular Material.

  <mat-form-field>
    <input matInput type="text" placeholder="{{ label }}" [(ngModel)]="_value"
           [required]="required" [disabled]="_disabled"
           (input)="intenrnalValueChange()" />
    <mat-hint>{{ hint }}</mat-hint>
    <mat-error>{{ getError() }}</mat-error>
  </mat-form-field>

The component is working well, except by a little trouble. I cannot mark it as touched, and I believe is due to the component architecture. I've tried some solutions of StackOverflow (and others) that work well in the traditional way but does not work for my component.

    this.form.get('field').markAsTouched(); // should work
    this.form.markAsTouched(); // should not work
    this.form.markAllAsTouched(); // should work

I want to mark the field touched by a button, which will show all the fields that are yet invalid (and their message by a mat-error).

I created a simplified project, where is possible to reproduce the problem. I want that when the button is clicked, the field becomes red and the message appears below. It does not happen, but if the field is clicked and blurred, the error is shown.

https://stackblitz.com/edit/angular-7mghym

The question is: how to change my project (keeping the component) in order to the button has the same effect to click and blur the field?

Paulo Candido
  • 167
  • 2
  • 9

1 Answers1

3

your approach has several problems;

first of all, you are implementing ControlValueAccessor but your InputComponent has an @Input named formControl. As a result of this when you bind form.controls.field to app-input your form is not interacting with ControlValueAccessor.

<app-input label="Test" hint="It is a test" [formControl]="form.controls.field" required></app-input>

As stated in docs;

ControlValueAccessor acts as a bridge between the Angular forms API and a native element in the DOM (html input element)

Second problem comes in here that; in InputComponent communication between DOM (html input element) and ControlValueAccessor is handled via ngModel which is fundamentally a wrapper around an explicitly createdFormControl under the hood.

As a result of these; form.controls.field FormControl has no communication with the actual input element, the ngModel has. That's why when you manually focus/blur input element it displays error status but this.form.get('field').markAsTouched(); doesn't do anything.

Third problem is that; even though you make ControlValueAccessor work by removing @Input formControl from InputComponent and communicate with html input without ngModel; still, mat-form-field will not be able to interact (i.e. getting error status) with underlying input element because it is required to implement MatFormFieldControl

Fourth problem is that when using ReactiveForms, validations (such as required in your case) and disabled state must be handled through FormControl not implicitly.

Good news is that these all problems have a very straightforward solution that without implementing ControlValueAccessor just pass the formControl to InputComponent and bind it to the html input.

Here is a working example; https://stackblitz.com/edit/angular-fwdrv9

Hope it helps, have a nice day.

ysf
  • 4,634
  • 3
  • 27
  • 29
  • thank you very much, it was very clear, but I yet have a question: if I do not implement `ControlValueAccessor`, I cannot use the component in other ways, such as with `[(ngModel)]`, right? Otherwise, how can I do it? – Paulo Candido Jun 13 '19 at 11:31
  • without ControlValueAccessor you cannot use ngModel. however; there is a way to mimic similar behavior of ngModel with two-way binding. see this post https://stackoverflow.com/a/50723598/1209097 – ysf Jun 13 '19 at 11:51