subform-control-value-accessor
v1.0.0
Published
Implemnted ControlValueAccessor interface abstract classes
Downloads
5
Maintainers
Readme
SubformControlValueAccessor
SubformControlValueAccessor is ControlValueAccessor interface implementation project for Angular Framework. Current project contains realized 2 abstract classes: ControlValueAccessorComponent for simple custom controls and FormControlValueAccessorComponent for subforms
Install
You can run this command in your terminal
$ npm install subform-control-value-accessor
Usage
ControlValueAccessorComponent
ControlValueAccessorComponent is abstract class for simple (not subform) custom form controls. This implementation support not only ControlValueAccessor interface functions but error managment too
Properties
Methods
Creating simple custom control
Module
Add FormModule to your module for ngModel
import { FormsModule } from '@angular/forms'; // add FormsModule for ngModel
@NgModule({
declarations: [
CustomComponent
],
imports: [
CommonModule,
FormsModule // add FormsModule for ngModel
],
exports: [
CustomComponent
]
})
export class CustomModule { }
Component class
Extend your class component from ControlValueAccessorComponent and set your control data type
export class CustomComponent extends ControlValueAccessorComponent<string>
Provide ControlContainer object to constructor
constructor(@Optional() @Host() @SkipSelf() controlContainer: ControlContainer){
super(controlContainer);
}
Implement abstract property data. ControlValueAccessorComponent has 2 getters and setters for linked data: "value" and "data" "data" is internal property without "onChange" event. This property is used by "writeValue" method ("ControlValueAccessor" interface) "value" is public property with "onChange" event. "value" === "data" + "onChange" event
private _data: string | null = null;
protected get data(): string | null { // data: T | null
return this._data;
}
protected set data(value : string | null){ // data: T | null
this._data = value;
}
Set provider
const CUSTOM_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomComponent),
multi: true
};
@Component({
selector: 'custom-component',
templateUrl: './custom.component.html',
providers: [CUSTOM_VALUE_ACCESSOR] //set provider
})
Full component code. You can find this code in examples.
import { Component, Host, Optional, SkipSelf, forwardRef } from '@angular/core';
import { ControlContainer, NG_VALUE_ACCESSOR } from '@angular/forms';
import { ControlValueAccessorComponent } from 'subform-control-value-accessor'; //import abstract class
//create provider for ControlValueAccessor
const CUSTOM_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomComponent),
multi: true
};
@Component({
selector: 'custom-component',
templateUrl: './custom.component.html',
providers: [CUSTOM_VALUE_ACCESSOR] //set provider
})
export class CustomComponent extends ControlValueAccessorComponent<string> { // extend class with ControlValueAccessorComponent<T>.
//ControlValueAccessorComponent<T> is generic class. Set your parameter type.
/**
* ControlValueAccessorComponent<T> uses ControlContainer parameter in constructor
*/
constructor(@Optional() @Host() @SkipSelf() controlContainer: ControlContainer){
super(controlContainer);
}
/**
* implement abstraction properties
*
* ControlValueAccessorComponent<T> has 2 getters and setters for linked data: "value" and "data"
* "data" is internal property without "onChange" event. This property is used by "writeValue" method ("ControlValueAccessor" interface)
* "value" is public property with "onChange" event.
* "value" === "data" + "onChange" event
*/
private _data: string | null = null;
protected get data(): string | null { //data: T | null
return this._data;
}
protected set data(value : string | null){ //data: T | null
this._data = value;
}
}
Component html
Create component template and link ngModel to PUBLIC "value" property
<input [(ngModel)]="value">
<p *ngFor="let error of activeErrorMessages">{{ error }}</p>
Using custom control in parent component with ngModel without form
Component class
Set data property for ngModel linking
export class ParentComponent {
public data: string = 'non-form data';
}
Component html
Create component template and link data property to ngModel
<custom-component [(ngModel)]="data"></custom-component>
<p>{{ data }}</p>
Using custom control in parent component with form
Component class
Set and initalize form property and object with custom error messages
export class ParentComponent {
constructor(private formBuilder: FormBuilder){}
public form!: FormGroup;
public errorMessages = {
'required': 'Field is required',
'minlength': (control: AbstractControl) => 'Min length is 4 characters'
};
public ngOnInit(): void {
this.form = this.formBuilder.group({
custom: ['custom control value', [Validators.required, Validators.minLength(4)]]
});
}
}
Component html
Create component template and link control to form
<form [formGroup]="form">
<label for="custom">Custom Control</label>
<custom-component
formControlName="custom"
name="custom"
style="display: block;"
[errorMessages]="errorMessages">
</custom-component>
</form>
<p>Form valid: {{ form.valid }}</p>
<p>Form value: {{ form.value | json }}</p>
FormControlValueAccessorComponent
FormControlValueAccessorComponent is abstract class for custom subform controls. This class extends ControlValueAccessorComponent and adds new functionality for subform controls.
Properties
Only new or overided properties are described here
Methods
Only new or overided methods are described here
Creating simple custom form control
Module
Add ReactiveFormsModule to your module
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@NgModule({
declarations: [
CustomFormComponent
],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule
]
})
export class CustomFormModule { }
Component class
Extend your class component from FormControlValueAccessorComponent and set your control data type
export class CustomFormComponent extends FormControlValueAccessorComponent<FormModel>
Provide ControlContainer and FormBuilder objects to constructor
constructor(
@Optional() @Host() @SkipSelf() controlContainer: ControlContainer,
private formBuilder: FormBuilder) {
super(controlContainer);
}
Implement form abstract getter
private _form!: FormGroup;
public get form(): FormGroup {
return this._form;
}
Implement initForm and patchForm methods
protected initForm(): void {
this._form = this.formBuilder.group({
firstName: ['John', [Validators.required]], //default form data
lastName: ['Doe', [Validators.required]] //default form data
});
}
protected patchForm(value: FormModel | null): void {
value && this.form.patchValue(value);
}
Create providers for NG_VALUE_ACCESSOR and NG_VALIDATORS and your component
const CUSTOM_FORM_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomFormComponent),
multi: true
};
const CUSTOM_FORM_VALIDATORS = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => CustomFormComponent),
multi: true
};
@Component({
selector: 'custom-form',
templateUrl: './custom-form.component.html',
providers: [ CUSTOM_FORM_VALUE_ACCESSOR, CUSTOM_FORM_VALIDATORS ] // Add providers to component decorator
})
Full component code. You can find this code in examples.
import { Component, Host, Optional, SkipSelf, forwardRef } from '@angular/core';
import { ControlContainer, FormBuilder, FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { FormControlValueAccessorComponent } from 'subform-control-value-accessor';
/**
* Component data structure
*/
interface FormModel {
firstName: string;
lastName: string
}
/**
* NG_VALUE_ACCESSOR provider for custom control
*/
const CUSTOM_FORM_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomFormComponent),
multi: true
};
/**
* NG_VALIDATORS provider for custom control
*/
const CUSTOM_FORM_VALIDATORS = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => CustomFormComponent),
multi: true
};
@Component({
selector: 'custom-form',
templateUrl: './custom-form.component.html',
providers: [ CUSTOM_FORM_VALUE_ACCESSOR, CUSTOM_FORM_VALIDATORS ] // Add providers to component decorator
})
export class CustomFormComponent extends FormControlValueAccessorComponent<FormModel> { // extend FormControlValueAccessorComponent and set your data type
private _form!: FormGroup;
constructor(
@Optional() @Host() @SkipSelf() controlContainer: ControlContainer,
private formBuilder: FormBuilder) {
super(controlContainer);
}
/**
* Implememt form getter
*/
public get form(): FormGroup {
return this._form;
}
/**
* Implement form initialization method
*/
protected initForm(): void {
this._form = this.formBuilder.group({
firstName: ['John', [Validators.required]], //default form data
lastName: ['Doe', [Validators.required]] //default form data
});
}
/**
* Implement for patching method
*/
protected patchForm(value: FormModel | null): void {
value && this.form.patchValue(value);
}
}
Component html
Create component template and link formGroup to "form" property
<form [formGroup]="form">
<label for="firstName">First Name</label>
<input formControlName="firstName" name="default" style="display: block;"/>
<ng-container *ngIf="this.form.controls['firstName'].invalid">
<p style="color:red" *ngIf="this.form.controls['firstName'].errors?.['required']">Field is required</p>
</ng-container>
<label for="lastName">Last Name</label>
<input formControlName="lastName" name="default" style="display: block;"/>
<ng-container *ngIf="this.form.controls['lastName'].invalid">
<p style="color:red" *ngIf="this.form.controls['lastName'].errors?.['required']">Field is required</p>
</ng-container>
</form>
Using custom control in parent component
Parent component class
Create and initialize FormGroup object
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
@Component({
selector: 'parent',
templateUrl: './parent.component.html'
})
export class ParentComponent implements OnInit {
constructor(private formBuilder: FormBuilder){}
public form!: FormGroup;
/**
* Initialize form. You can set default value to custom component. If you set null then component uses component default value
*/
public ngOnInit(): void {
this.form = this.formBuilder.group({
custom: [{firstName: 'Jane', lastName: 'Doe'}],
});
}
}
Parent component html
Create form template and provide custom component as form control
<form [formGroup]="form">
<custom-form formControlName="custom"></custom-form>
</form>
<p>Form Valid: {{ form.valid }}</p>
<p>Form Value: {{ form.value | json }}</p>
Summary
All examples you can find in git project page Good luck!