Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

mat-error put to separate component not rendering properly

I want to use mat-errors to render server-side errors in my Angular 5 SPA.

This is what I've got so far, and it works

<mat-form-field class="col-6">
    <input matInput formControlName="firstName">
    <mat-error [hidden]="!form.controls.firstName.hasError('required')">This field is required and cannot be empty</mat-error>
    <mat-error [hidden]="!form.controls.firstName.hasError('other')">Some other error</mat-error>
</mat-form-field>

Every field of the form looks similar. Input field and many mat-error tags below. There is lots of repetitive code attached to single input field. Also, adding a new error message would cause adding it in the every field that needs it. I think it is a good space to introduce component that manages error messages, and injected with form control it decides which error to show (I want to have common error messages for all fields).

So I would like to have it this way

<mat-form-field class="col-6">
    <input matInput formControlName="firstName">
    <app-mat-errors [field]="form.controls.firstName"></app-map-errors>
</mat-form-field>

and in the app-mat-errors component template have all the mat-error code that we used to have in every single field

<mat-error [hidden]="!field.hasError('required')">This field is required and cannot be empty</mat-error>
<mat-error [hidden]="!field.hasError('other')">Some other error</mat-error>
etc....

With this approach I'm having a problem with rendering component correctly.

When put to the <app-mat-errors>, <mat-error> tags are embedded in app-mat-errors tag and are not showing properly (they are not styled and visible all the time, even when there is no error)

Is there any way angular can render component wihout parent tag? Or you have any ideas how to make it working properly? Thanks in advance.

like image 669
hopsey Avatar asked May 22 '18 12:05

hopsey


2 Answers

The <mat-error> elements need to be direct children of <mat-form-field> in order to work properly, which is what you have discovered. So you can't wrap <mat-error> in another component and use that component inside <mat-form-field> in place of <mat-error>.

However, you don't need to have a one to one relationship between your errors and <mat-error> elements - <mat-error> is not a special object - it is just the container where <mat-form-field> displays error content. So you can put your error logic inside a component and use the component inside a single <mat-error> instead. Here's how that might look:

Form field HTML:

<mat-form-field class="col-6">
    <input matInput formControlName="firstName">
    <mat-error>
        <app-mat-errors [field]="form.controls.firstName"></app-map-errors>
    </mat-error>
</mat-form-field>

Errors component template:

<ng-container *ngIf="field.hasError('required')">This field is required and cannot be empty</ng-container>
<ng-container *ngIf="field.hasError('other')">Some other error</ng-container>
etc....

If your field can have more than one error, be sure to account for that in your *ngIf logic in order to avoid having more than one message appear.

You might also want to consider implementing the logic as a function rather than as a component, where it may be easier to write the logic:

<mat-form-field class="col-6">
    <input matInput formControlName="firstName">
    <mat-error>
        {{getErrorMessage(form.controls.firstName)}}
    </mat-error>
</mat-form-field>

getErrorMessage(control: FormControl): string {
    if (control) {
        if (control.hasError('required')) return 'This field is required and cannot be empty';
        if (control.hasError('other')) return 'Some other error';
        // etc.
    }
    return '';
}
like image 72
G. Tranter Avatar answered Nov 05 '22 21:11

G. Tranter


As established, mat-error does not accept a component. But you can use your component by using it as an attribute selector. So place your component as an attribute for mat-error. First you need to change your selector in child component to

@Component({
  selector: '[app-mat-errors]',
  // ...
})

Notice the brackets, that is how we tell Angular that we want to use this component as an attribute.

As for the template, your child template needs to have the mat-error tags removed, you can instead for example use a span:

<span [hidden]="!field.hasError('required')">This field is required and cannot be empty</span>
<span [hidden]="!field.hasError('other')">Some other error</span>

Then as for the parent component, you just attach the child as an attribute to mat-error:

<mat-form-field>
  <input matInput formControlName="firstName">
  <mat-error app-mat-errors [field]="form.controls.firstName"></mat-error>
</mat-form-field>

And that's it! :)

like image 1
AT82 Avatar answered Nov 05 '22 21:11

AT82