Reputation: 189
We use many Angular material fields and they are all presented the same. I want a way to simplify any changes coming from UI designers and maintenance later.
How can I create a component that reduces this block of HTML (just example code)
<div fxFlex>
<mat-form-field>
<mat-label>The Label</mat-label>
<input matInput formControlName="controlName" ... >
<mat-hint>*Required</mat-hint>
<mat-error *ngIf="controlName.invalid && controlName.touched">{{errorMessage}}</mat-error>
</mat-form-field>
<div>
down to something sensible like this
<custominput formControlName="controlName" [errorMessage]="Error" ...></custominput>
I've tried to follow along with these tutorials without success.
Angular material Custom Fields
I really don't understand how to apply to an input field essentially. This is what I'm left with at present.
TS file contents
import { Component, ChangeDetectionStrategy, Input, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MatInput } from '@angular/material/input';
@Component({
selector: 'hr-textinput',
template:`
<div [ngClass]="labelPosition">
<mat-label>{{label}}</mat-label>
<mat-form-field>
<input #inputFieldIdentifier matInput formControlName={{controlName}} id={{id}} (blur)="onTouched()">
<mat-error *ngIf="controlName.invalid">{{errorMsg}}</mat-error>
<mat-hint *ngIf="mandatory">*Required</mat-hint>
</mat-form-field>
</div>
`,
styleUrls: ['./textinput.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi:true,
useExisting: TextinputComponent
}
]
})
export class TextinputComponent implements ControlValueAccessor, OnInit {
@ViewChild('inputFieldIdentifier') inputField: MatInput;
@Input() controlName: string = "";
@Input() mandatory: boolean = false;
@Input() label: string = "";
@Input() errorMsg = "";
@Input() labelPosition: string = "before"; // "before" and "above" are the two options available at present
@Input() id: string = "";
touched = false;
disabled = false;
constructor() {
}
ngOnInit(): void {
this.id = `${this.controlName}_ID`;
}
onChange = (value) => {};
onTouched = () => {};
writeValue(fieldValue: any): void {
this.inputField.value = fieldValue;
}
registerOnChange(onChangeFn: any): void {
this.onChange = onChangeFn;
}
registerOnTouched(onTouchedFn: any): void {
this.onTouched = onTouchedFn;
}
setDisabledState?(isDisabled: boolean): void {
this.inputField.disabled = isDisabled;
}
markAsTouched() {
if (!this.touched) {
this.onTouched();
this.touched = true;
}
}
}
SCSS file contents
.above {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: left;
.mat-form-field {
padding-top: 6px;
}
}
.before {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
.mat-form-field {
padding-left: 12px;
}
}
Any thoughts are greatly appreciated as I've clearly no idea.
Upvotes: 0
Views: 267
Reputation: 2379
The reason you are seeing the The app cannot find my 'form'
error is because you are using the formControlName
directive inside the component that is supposed to be implementing the formControlName
interface. If you remove the formControlName
directive from your custom input component template, you will no longer see that error.
In other words, when implementing ControlValueAccessor
you are essentially telling Angular that your component knows how to use the formControlName
directive and so the formControlName
directive should be put on your custom component when instantiating it. i.e.
<hr-textinput formControlName="myField">
</hr-textinput>
I forked your stackblitz to show a working form: https://stackblitz.com/edit/angular-ivy-onycez?
Upvotes: 1