#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
This is the component as it is
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.