How To make a reusable matInputField with Angular Material

#Angular #Material #Compoents #ReusableComponets

here is the full tutorial on how to build a reusable Matfield component in angular without *ControlValueAccessor


this tutorial includes building the input field and input validation.

After so many researches on how to build a reusable MatInputField that includes Form validation without using ControlValueAccessor. I got stuck and tried to do it from scratch.

there are so many tutorials out there but none of them worked as I want. because when we say reusable we think that this component should include everything validation, style...

I didn't do style because material fields already come with good styling. I'm writing this blog (this is my first blog ever BTW) and hoping that I could help someone with my attempt.

taking on the consideration that I'm a beginner angular developer. enough with the talks Let's get this tutorial started.

Reusable Components Why??

Reusable components appear in almost every modern web application. Not only do we have to reuse the same components in complex business applications, but we also have to reuse them in a different context.

When reusing a component in a different business context it's important that a part of the component is flexible and can be adjusted to the use case.

Imagine tabs, cards, or an expander. All those elements have a similar base structure and behavior but the content can differ. We need a way to integrate flexible content with a reusable base.

Modern web technologies and especially Angular offer us great mechanisms to implement such components.

obviously you should have Angular installed and Material : if you don't have material run this command

ng add @angular/material

and you should have MatFormFieldModule and MatInputModule Both Imported in your module.

Results

beforeERR.png

This is the component as it is

normal.png

afterERR.png This is the field with Input Validation

Lets Start

1. Create the Reusable Component

You could create it from scratch or Run this command

ng generate component ComponentName

I'm using the name : MatInputFieldComponent

Here is the code of the component ts File :

import {
  ControlContainer,
  FormControl,
  FormGroupDirective
} from '@angular/forms';
import { FormGroup, FormBuilder } from '@angular/forms';

import {
  Component,
  OnInit,
  Input,
  ViewChild,
  ContentChild
} from '@angular/core';
import {
  MatFormField,
  MatFormFieldControl
} from '@angular/material/form-field';

@Component({
  selector: 'app-mat-input-field',
  templateUrl: './mat-input-field.component.html',
  styleUrls: ['./mat-input-field.component.scss'],
  viewProviders: [
    { provide: ControlContainer, useExisting: FormGroupDirective }
  ]
})
export class MatInputFieldComponent implements OnInit {
  @Input() public label: string = '';
  @Input() public hint: string = '';
  @Input() public type: 'text' | 'number' | 'email' | 'password' = 'text';
  @Input() public control: any;
  @Input() public controlName: string = '';
  @Input() public appearance: 'legacy' | 'fill' | 'standard' | 'outline' =
    'standard';
  formGroup!: FormGroup;
  constructor(
    private ctrlContainer: FormGroupDirective,
    private formBuilder: FormBuilder
  ) {}
  @ContentChild(MatFormFieldControl, { static: true })
  public formFieldControl!: MatFormFieldControl<any>;

  @ViewChild('materialFormField', { static: true })
  public matFormField!: MatFormField;

  public ngOnInit() {
    this.matFormField._control = this.formFieldControl;
    this.formGroup = this.ctrlContainer.form;
    const control = new FormControl('');
    this.formGroup.addControl(this.controlName, control);
  }
}

Angular Material's form field has issues with content projection when material input is entered in the ng-content area. The property formFieldControl defines as a MaterialFormFiedlControl so the control may be overridden.

The matFormField property grabs the Material Form Field. ngOnInit helps define the _control property of the matFormField, suppressing errors complaining of missing material inputs.

And we are using the @Input() to bind the properties .

Here is the code of the component Html File

  #materialFormField
  class="mat-input-full-width"
  [appearance]="appearance"
>
  <input matInput type="{{ type }}" [formControlName]="controlName" />

  <span matPrefix>
    <ng-content select="[prefix]"></ng-content>
  </span>
  <mat-label
    ><span class="MatLabel">{{ label }}</span></mat-label
  >
  <mat-hint
    ><span class="MatHint">{{ hint }}</span></mat-hint
  >
  <ng-content></ng-content>
  <span matSuffix>
    <ng-content select="[suffix]"></ng-content>
  </span>
  <mat-error *ngIf="control.hasError('required')">
    This Field is required
  </mat-error>
  <mat-error
    *ngIf="
      control.hasError('email') ||
      control.hasError('pattern') ||
      control.hasError('minLength') ||
      control.hasError('maxLength')
    "
  >
    Please enter a Valid {{ label }}
  </mat-error>
</mat-form-field>

And this is how you can use it

In your Reactive Form group Create Form Control and give the name of the control to the properties

FormGroupName=new formGroup({
       nameOfControl: new formControl('',[Validators.require,Validators.email])
})

The properties control and controlName are necessary but the other one you could just not call them.

<!-- Usage  -->

<app-mat-input-field
  [label]="'Label ...'"
  [hint]="'Hint ....'"
  [type]="'emai'"
  [control]="FormGroupName.get('nameOfControl')"
  [controlName]="'NameOfControl'"
  [appearance]="'legacy'"
>
</app-mat-input-field>

<!-- Default  -->

<app-mat-input-field
  [control]="FormGroupName.get('nameOfControl')"
  [controlName]="'NameOfControl'"
></app-mat-input-field>

Thank you for making it here, I hope that this tutorial helped in some ways if you have any suggestions to improve my code leave it in the comments.