The task I am trying to solve:
Create re-usable input component wrappers to save time when writing template for forms.
Example of what I mean:
Instead of having to write the following template:
<form [formGroup]="myForm">
<mat-form-field>
<input matInput placeholder="Email" [formControl]="email" required>
<mat-error *ngIf="email.invalid">{{getErrorMessage()}}</mat-error>
</mat-form-field>
<form>
I would like to write:
<form [formGroup]="myForm">
<my-input-component [form]="myForm" [myFormControl]="email" [myFormControlName]="'email'" [label]="'Email'"></my-input-component>
</form>
Where my-input-component looks like:
<mat-form-field [formGroup]="form">
<input
matInput
type="text"
[attr.inputmode]="inputMode"
[placeholder]="label"
[formControlName]="myFormControlName"
/>
<mat-error class="errors" *ngIf="myFormControl.invalid">
<div>{{ getError() }}</div>
</mat-error>
</mat-form-field>
This is working as is but I don't know if this is a good approach to pass the FormGroup and FormControls around as bindings.
After searching online I have continuously come across NG_CONTROL_VALUE_ACCESSOR and was a bit confused if that could or should be used in my scenario.
I don't intend for these components to be "custom" in the sense of using a slider as a form control or something of that nature, but rather just wanted "wrappers" to save some time.
Any suggestions or advice on the topic would be greatly appreciated!
The recommended way to achieve this is, like you have already found out, to implement the ControlValueAccessor interface. This interface was created specifically to create custom form controls. It will create a bridge between your reusable component and the Forms API.
Here's a small example of a reusable input field with a label. You could add your error messages in this template as well.
Component
import { Component, OnInit, Input, Self, Optional } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
@Component({
selector: 'custom-input',
templateUrl: './custom-input.component.html',
styleUrls: ['./custom-input.component.css']
})
export class CustomInputComponent implements OnInit, ControlValueAccessor {
@Input() disabled: boolean;
@Input() label: string;
@Input() placeholder: string = '';
@Input() type: 'text' | 'email' | 'password' = 'text';
value: any = '';
constructor(
// Retrieve the dependency only from the local injector,
// not from parent or ancestors.
@Self()
// We want to be able to use the component without a form,
// so we mark the dependency as optional.
@Optional()
private ngControl: NgControl
) {
if (this.ngControl) {
this.ngControl.valueAccessor = this;
}
}
ngOnInit() {}
/**
* Write form value to the DOM element (model => view)
*/
writeValue(value: any): void {
this.value = value;
}
/**
* Write form disabled state to the DOM element (model => view)
*/
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
/**
* Update form when DOM element value changes (view => model)
*/
registerOnChange(fn: any): void {
// Store the provided function as an internal method.
this.onChange = fn;
}
/**
* Update form when DOM element is blurred (view => model)
*/
registerOnTouched(fn: any): void {
// Store the provided function as an internal method.
this.onTouched = fn;
}
private onChange() {}
private onTouched() {}
}
Template
<label>{{ value }}</label>
<input [type]="type"
[placeholder]="placeholder"
[value]="value"
[disabled]="disabled"
(input)="onChange($event.target.value)"
(blur)="onTouched()" />
You can check out this article Creating a custom form component in Angular for more detailed information.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With