Angular - How to create a data aware custom component
Introduction
This blog will demonstrate how to create an Angular component that you are able to add [ngModel] [formControl] and [formControlName] attributes to your custom component, and have your component correct implement the features required to work with Angular forms.
Setting up the ngModule
First add FormsModule and ReactiveFormsModule to your main NgModule's import declaration
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, FormsModule, ReactiveFormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Creating the custom component's template
Note: If you have installed @angular/cli using npm you can type ng g component /components/custom-input in a command prompt to create the component + template + test cases.
Next create a new component with the following html template
My custom input <input (blur)="blurred()" (focus)="focused()" [(ngModel)]="value" />
The custom component's class code
First you need to import a few references
import { Component, forwardRef, Input } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
What these identifiers do:
- NG_VALUE_ACCESSOR: A marker in the component's providers declaration to indicate that the new component implements ControlValueAccessor
- ControlValueAccessor: The interface that needs to be implemented in order to support data-binding through [formControl] and [formControlName]
Now update the component's @Component declaration and add providers, and update the selector.
@Component({ selector: '[formControl] custom-input, [formControlName] custom-input', templateUrl: './custom-input.component.html', styleUrls: ['./custom-input.component.css'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CustomInputComponent), multi: true } ] })
And finally, the component's class code
export class CustomInputComponent implements ControlValueAccessor { private _value: any; private hasHadFocus = false; private hasNotifiedTouched = false; private propagateChange: any = () => {}; private propogateTouched: any = () => {}; public get value(): any { return this._value; } @Input('value') public set value(value: any) { this._value = value; this.propagateChange(value); } /** * Called when input (focus) is triggered */ public focused() { this.hasHadFocus = true; } /** * Called when input (blur) is triggered */ public blurred() { if (this.hasHadFocus && !this.hasNotifiedTouched) { this.hasNotifiedTouched = true; this.propogateTouched(); } } /** * Called when a new value is set via code * @param obj */ writeValue(value: any): void { this.value = value; } /** * Register Angular's call back to execute when our value changes * @param fn */ registerOnChange(fn: any): void { this.propagateChange = fn; } /** * Register Angular's call back to execute when our value is first touched * @param fn */ registerOnTouched(fn: any): void { this.propogateTouched = fn; } }
Comments