Loading...
So lets talk a bit about angular...
It's common that we will build custom form elements of our own, and we would like those form elements to be ng modelable meaning we would like to be able to use ngModel directive and 2 way binding on those elements.
This tutorial will explain how to create your form control which you can attach to ngModel.
We want to create a custom form control and make angular aware that this is a form control.
We will create a component with a text input (ngModel will be connected to this input)
in order to be able to attach ngModel we need to implement the interface: ControlValueAccessor
We need to implement some methods from which ngModel will access the value and state of our form control.
with ControlValueAccessor we will need to implement the following methods:
we need to introduce our custom form element to angular.
We do this through the NG_VALUE_ACCESSOR.
We need to supply a provider for the token: NG_VALUE_ACCESSOR
We can also create custom validation for our controller, by implementing the interface: Validator.
There is a single method which we need to implement here which is called validate(c: AbstractControl)
This method will return a dictionary with the errors.
After we implement the interface we need to make sure that angular knows that this control has custom validation in it.
We do this by adding the token: NG_VALIDATORS
To our component providers
our custom form control component will look like this:
import {Component, ElementRef, forwardRef, ViewChild} from '@angular/core';
import {ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator} from "@angular/forms";
import {ValidationErrors} from "@angular/forms/src/directives/validators";
import {AbstractControl} from "@angular/forms/src/model";
@Component({
selector: 'app-custom-input',
template: `
<div class="form-group">
<label>Custom control</label>
<input
type="text"
name="custom-control"
class="form-control"
(blur)="inputBlurred()"
#textInput (change)="userTypes($event)" />
</div>
`,
providers: [
{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CustomInputComponent), multi: true},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => CustomInputComponent),
multi: true,
}
],
})
export class CustomInputComponent implements ControlValueAccessor, Validator{
@ViewChild('textInput') public inputElement: ElementRef;
private _cb: (_: any) => void;
private _cbBlurred: any;
writeValue(value: string) {
this.inputElement.nativeElement.value = value;
}
registerOnChange(fn: (_: any) => void) {
this._cb = fn;
}
userTypes(event) {
this._cb(event.target.value);
}
registerOnTouched(fn: any) {
this._cbBlurred = fn;
}
inputBlurred() {
this._cbBlurred();
}
validate(c: AbstractControl): ValidationErrors | null {
if (c.value === 'nerdeez') return null;
return {
'BAD_INPUT': ['input has to be nerdeez']
}
}
}
Now in our parent component template, we can do this
<app-custom-input
required
#titleInput="ngModel"
[(ngModel)]="title" name="title" label="title"></app-custom-input>
{{titleInput.errors | json}}
notice that we are using ngModel directive, we can add validation like required, we can use the ngModel class properties and methods.
Our custom form control can mean wrapping form control for entire classes, and not just simple form elements like text input.
Let's say we have the following simple todo class
export class Todo {
constructor(public title: string, public description: string) {}
}
We want a custom form control which will accept a todo instance in the ngModel.
This form control can have custom validation on the instance and can change the properties of the instance we pass.
our custom form control will now look like this:
import {Component, forwardRef} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from "@angular/forms";
import {Todo} from "../todo";
@Component({
selector: 'app-todo-form-control',
template: `
<input type="text" name="title" [value]="todo?.title"
(blur)="callBlur()"
(input)="todo.title=$event.target.value; callChangeCb()" />
<textarea [value]="todo?.description"
(blur)="callBlur()"
(input)="todo.description=$event.target.value; callChangeCb()"></textarea>
`,
styleUrls: ['./todo-form-control.component.scss'],
providers: [
{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TodoFormControlComponent), multi: true}
]
})
export class TodoFormControlComponent implements ControlValueAccessor{
public todo: Todo;
private _changeCb: any;
private _blurCb: any;
constructor() { }
writeValue(obj: Todo): void {
this.todo = obj;
}
registerOnChange(fn: any) {
this._changeCb = fn;
}
registerOnTouched(fn: any) {
this._blurCb = fn;
}
callChangeCb() {
this._changeCb(this.todo);
}
callBlur() {
this._blurCb();
}
}
so in this case our custom control is getting in the ngModel and instance of a class and builds an entire form for that instance.
With this in mind, start thinking about your company utilities for forms.
Maybe you can already create a FormsModule with your custom form components.