San Jaisy
San Jaisy

Reputation: 17128

Angular material mat-error not working with component

I have a common component as below, where I am using mat-error

@Component({
  selector: 'falcon-validation-error',
  standalone: true,
  imports: [CommonModule, MatInputModule, ],
  template: `
    <mat-error>kkk</mat-error>
  `,
})
export class ValidationErrorComponent {}

In the HTML of another component

  <mat-form-field>
    <mat-label>Enter your email</mat-label>
    <input matInput
           placeholder="[email protected]"
           [formControl]="email"
           required>
    @if (email.invalid) {
      <falcon-validation-error/>

    }
  </mat-form-field>

email = new FormControl('', [Validators.required, Validators.email]);

The design looks ugly

enter image description here

However, if I directly use the mat-error

  <mat-form-field>
    <mat-label>Enter your email</mat-label>
    <input matInput
           placeholder="[email protected]"
           [formControl]="email"
           required>
    @if (email.invalid) {
      <mat-error>kkk</mat-error>

    }
  </mat-form-field>

enter image description here

Update

I have created a common component for error display. For example, in the textbox component, I am calling the falcon-error component to display the error

@Component({
  selector: 'falcon-textbox',
  standalone: true,
  imports: [MatInputModule, ...sharedControlDeps],
  viewProviders: [controlProvider],
  template: `
    <mat-form-field appearance="outline" class="w-full">
      <mat-label>{{ control.config.label }}</mat-label>
      <input matInput [formControlName]="control.formControlName" [placeholder]="control.config.placeHolder"
             [container]="containerDir.container">
    </mat-form-field>
    <falcon-error [error]=“errors”/>
  `,
  styles: `.w-full {
    width: 100%
  }`,
})
export class TextboxComponent extends BaseControlBuilder {
}




@Component({
  selector: 'falcon-error',
  standalone: true,
  imports: [CommonModule, MatInputModule, ErrorMessagePipe],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    @if (errors) {
      @for (error of errors | keyvalue; ) {
        <mat-error>{{ error.key | errorMessage:error.value }}</mat-error>
      }
    }
  `,
})
export class ValidationErrorComponent {
  @Input() errors: ValidationErrors | undefined | null = null;
  
}

Upvotes: 0

Views: 438

Answers (2)

Eliseo
Eliseo

Reputation: 58019

It's a bit complex and change with the changes of material angular.

In this SO show how join two errors using an unique mat-error and inside a list of errors.

Hardly based in this link, we can make a component. The problem it's that material-angular changes how show the errors, so I put the code only for the last version of material. In this version the only we need is change the height of the div with class "mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align".

The most important part is get the FormField and the control. For this we inject in constructor the @Host()of matformField

 constructor(@Host() private formField: MatFormField) {}

And in ngAfterViewInit

ngAfterViewInit() {
    this.control =
      this.formField?._formFieldControl?.ngControl?.control || null;
    ...
}

With the new Angular we can subscribe to control.events to know when we need change the height

 ngAfterViewInit() {
    this.control =
      this.formField?._formFieldControl?.ngControl?.control || null;
    if (this.control)
      this.subscription = this.control.events.subscribe((res: any) => {
        const wrapper =
          this.formField._elementRef.nativeElement.getElementsByClassName(
            'mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align'
          );
        const numError =
          this.control?.touched && this.control?.errors
            ? Object.keys(this.control?.errors).length
            : 0;
        const hints = this.formField._hintChildren.length;
        const height = !numError ? '' : 8 + numError * 16 + 'px';
        if (wrapper.length) {
          (wrapper[0] as HTMLElement).style.height = height;
        }
      });
  }

Now just define the template and a bit of the .css

@Component({
  selector: '[mat-error-custom]',
  standalone: true,
  template:`
  <ul class="ul-clear">
        @for(error of getError();track $index){
        <li>{{error.error}}</li>
        }
      </ul>
  `,
  styles:[`
  ul.ul-clear{
    display: block;
    list-style-type: disc;
    margin-block-start: -16px;
    margin-block-end: 0;
    margin-inline-start: 0px;
    margin-inline-end: 0px;
    padding-inline-start: 0;
    unicode-bidi: isolate;
}
  `]
})

And define the function getErrors()

  getError() {
    let error: any[] = [];
    if (this.control?.errors) {
      Object.keys(this.control.errors).forEach((firstKey) => {
        switch (firstKey) {
          case 'required':
            error.push({ error: 'required' });
            break;
          case 'minlength':
            error.push({
              error:
                ' is required ' +
                this.control?.errors?.minlength.requiredLength +
                ' characteres',
            });
            break;
          case 'pattern':
            error.push({ error: " don't match with pattern" });
            break;
          case 'maxlength':
            error.push({
              error:
                ' is required less than ' +
                this.control?.errors?.maxlength.requiredLength +
                ' characteres',
            });
            break;
          default:
            error.push({error:this.control?.errors?.errorText})
        }
      });
    }
    return error.length ? error : null;
  }

the stackblitz

Upvotes: 0

Eliseo
Eliseo

Reputation: 58019

I feel that the best bet is "extending" the mat-error directive. Well if you create a directive with the same selector of mat-error, each time you use mat-error your "mat-error" is a "super" mat-error and you can access to the mat-error and the elementRef

@Directive({
  selector: 'mat-error, [matError]',
  standalone: true,
})
export class CustomError implements OnInit {
    constructor(private elementRef: ElementRef,private matError:MatError)
    {
      console.log(this.matError,this.elementRef)
    }
}
ngOnInit()
{
   I can do anything
}

You can use also any selector and write

<mat-error super-mat-error>this is an error</mat-error>

But really I don't know what are you trying to do

Upvotes: 0

Related Questions