refactor(forms): add base class for all built-in ControlValueAccessors (#41225)

This commit adds a base class that contains common logic for all ControlValueAccessors defined in Forms package. This allows to remove duplicated logic from all built-in ControlValueAccessor classes.

PR Close #41225
This commit is contained in:
Andrew Kushnir
2021-03-15 18:56:06 -07:00
committed by Zach Arend
parent 44a7fae00f
commit 51bb922a08
11 changed files with 107 additions and 291 deletions
@@ -47,51 +47,11 @@ export const CHECKBOX_VALUE_ACCESSOR: any = {
})
export class CheckboxControlValueAccessor extends BuiltInControlValueAccessor implements
ControlValueAccessor {
/**
* The registered callback function called when a change event occurs on the input element.
* @nodoc
*/
onChange = (_: any) => {};
/**
* The registered callback function called when a blur event occurs on the input element.
* @nodoc
*/
onTouched = () => {};
constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {
super();
}
/**
* Sets the "checked" property on the input element.
* @nodoc
*/
writeValue(value: any): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'checked', value);
}
/**
* Registers a function called when the control value changes.
* @nodoc
*/
registerOnChange(fn: (_: any) => {}): void {
this.onChange = fn;
}
/**
* Registers a function called when the control is touched.
* @nodoc
*/
registerOnTouched(fn: () => {}): void {
this.onTouched = fn;
}
/**
* Sets the "disabled" property on the input element.
* @nodoc
*/
setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
this.setProperty('checked', value);
}
}
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {InjectionToken} from '@angular/core';
import {Directive, ElementRef, InjectionToken, Renderer2} from '@angular/core';
/**
* @description
@@ -132,13 +132,75 @@ export interface ControlValueAccessor {
}
/**
* Base class for all built-in ControlValueAccessor classes. We use this class to distinguish
* between built-in and custom CVAs, so that Forms logic can recognize built-in CVAs and treat
* custom ones with higher priority (when both built-in and custom CVAs are present).
* Base class for all ControlValueAccessor classes defined in Forms package.
* Contains common logic and utility functions.
*
* Note: this is an *internal-only* class and should not be extended or used directly in
* applications code.
*/
export class BuiltInControlValueAccessor {}
@Directive()
export class BaseControlValueAccessor {
/**
* The registered callback function called when a change or input event occurs on the input
* element.
* @nodoc
*/
onChange = (_: any) => {};
/**
* The registered callback function called when a blur event occurs on the input element.
* @nodoc
*/
onTouched = () => {};
constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {}
/**
* Helper method that sets a property on a target element using the current Renderer
* implementation.
* @nodoc
*/
protected setProperty(key: string, value: any): void {
this._renderer.setProperty(this._elementRef.nativeElement, key, value);
}
/**
* Registers a function called when the control is touched.
* @nodoc
*/
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
/**
* Registers a function called when the control value changes.
* @nodoc
*/
registerOnChange(fn: (_: any) => {}): void {
this.onChange = fn;
}
/**
* Sets the "disabled" property on the range input element.
* @nodoc
*/
setDisabledState(isDisabled: boolean): void {
this.setProperty('disabled', isDisabled);
}
}
/**
* Base class for all built-in ControlValueAccessor classes (except DefaultValueAccessor, which is
* used in case no other CVAs can be found). We use this class to distinguish between default CVA,
* built-in CVAs and custom CVAs, so that Forms logic can recognize built-in CVAs and treat custom
* ones with higher priority (when both built-in and custom CVAs are present).
*
* Note: this is an *internal-only* class and should not be extended or used directly in
* applications code.
*/
@Directive()
export class BuiltInControlValueAccessor extends BaseControlValueAccessor {
}
/**
* Used to provide a `ControlValueAccessor` for form controls.
@@ -9,7 +9,7 @@
import {ɵgetDOM as getDOM} from '@angular/common';
import {Directive, ElementRef, forwardRef, Inject, InjectionToken, Optional, Renderer2} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
import {BaseControlValueAccessor, ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
export const DEFAULT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
@@ -83,25 +83,14 @@ export const COMPOSITION_BUFFER_MODE = new InjectionToken<boolean>('CompositionE
},
providers: [DEFAULT_VALUE_ACCESSOR]
})
export class DefaultValueAccessor implements ControlValueAccessor {
/**
* The registered callback function called when an input event occurs on the input element.
* @nodoc
*/
onChange = (_: any) => {};
/**
* The registered callback function called when a blur event occurs on the input element.
* @nodoc
*/
onTouched = () => {};
export class DefaultValueAccessor extends BaseControlValueAccessor implements ControlValueAccessor {
/** Whether the user is creating a composition string (IME events). */
private _composing = false;
constructor(
private _renderer: Renderer2, private _elementRef: ElementRef,
renderer: Renderer2, elementRef: ElementRef,
@Optional() @Inject(COMPOSITION_BUFFER_MODE) private _compositionMode: boolean) {
super(renderer, elementRef);
if (this._compositionMode == null) {
this._compositionMode = !_isAndroid();
}
@@ -113,31 +102,7 @@ export class DefaultValueAccessor implements ControlValueAccessor {
*/
writeValue(value: any): void {
const normalizedValue = value == null ? '' : value;
this._renderer.setProperty(this._elementRef.nativeElement, 'value', normalizedValue);
}
/**
* Registers a function called when the control value changes.
* @nodoc
*/
registerOnChange(fn: (_: any) => void): void {
this.onChange = fn;
}
/**
* Registers a function called when the control is touched.
* @nodoc
*/
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
/**
* Sets the "disabled" property on the input element.
* @nodoc
*/
setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
this.setProperty('value', normalizedValue);
}
/** @internal */
@@ -48,23 +48,6 @@ export const NUMBER_VALUE_ACCESSOR: any = {
})
export class NumberValueAccessor extends BuiltInControlValueAccessor implements
ControlValueAccessor {
/**
* The registered callback function called when a change or input event occurs on the input
* element.
* @nodoc
*/
onChange = (_: any) => {};
/**
* The registered callback function called when a blur event occurs on the input element.
* @nodoc
*/
onTouched = () => {};
constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {
super();
}
/**
* Sets the "value" property on the input element.
* @nodoc
@@ -72,7 +55,7 @@ export class NumberValueAccessor extends BuiltInControlValueAccessor implements
writeValue(value: number): void {
// The value needs to be normalized for IE9, otherwise it is set to 'null' when null
const normalizedValue = value == null ? '' : value;
this._renderer.setProperty(this._elementRef.nativeElement, 'value', normalizedValue);
this.setProperty('value', normalizedValue);
}
/**
@@ -84,20 +67,4 @@ export class NumberValueAccessor extends BuiltInControlValueAccessor implements
fn(value == '' ? null : parseFloat(value));
};
}
/**
* Registers a function called when the control is touched.
* @nodoc
*/
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
/**
* Sets the "disabled" property on the input element.
* @nodoc
*/
setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}
}
@@ -124,16 +124,13 @@ export class RadioControlValueAccessor extends BuiltInControlValueAccessor imple
/**
* The registered callback function called when a change event occurs on the input element.
* Note: we declare `onChange` here (also used as host listener) as a function with no arguments
* to override the `onChange` function (which expects 1 argument) in the parent
* `BaseControlValueAccessor` class.
* @nodoc
*/
onChange = () => {};
/**
* The registered callback function called when a blur event occurs on the input element.
* @nodoc
*/
onTouched = () => {};
/**
* @description
* Tracks the name of the radio input element.
@@ -156,9 +153,9 @@ export class RadioControlValueAccessor extends BuiltInControlValueAccessor imple
@Input() value: any;
constructor(
private _renderer: Renderer2, private _elementRef: ElementRef,
private _registry: RadioControlRegistry, private _injector: Injector) {
super();
renderer: Renderer2, elementRef: ElementRef, private _registry: RadioControlRegistry,
private _injector: Injector) {
super(renderer, elementRef);
}
/** @nodoc */
@@ -179,7 +176,7 @@ export class RadioControlValueAccessor extends BuiltInControlValueAccessor imple
*/
writeValue(value: any): void {
this._state = value === this.value;
this._renderer.setProperty(this._elementRef.nativeElement, 'checked', this._state);
this.setProperty('checked', this._state);
}
/**
@@ -203,22 +200,6 @@ export class RadioControlValueAccessor extends BuiltInControlValueAccessor imple
this.writeValue(value);
}
/**
* Registers a function called when the control is touched.
* @nodoc
*/
registerOnTouched(fn: () => {}): void {
this.onTouched = fn;
}
/**
* Sets the "disabled" property on the input element.
* @nodoc
*/
setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}
private _checkName(): void {
if (this.name && this.formControlName && this.name !== this.formControlName &&
(typeof ngDevMode === 'undefined' || ngDevMode)) {
@@ -52,29 +52,12 @@ export const RANGE_VALUE_ACCESSOR: StaticProvider = {
})
export class RangeValueAccessor extends BuiltInControlValueAccessor implements
ControlValueAccessor {
/**
* The registered callback function called when a change or input event occurs on the input
* element.
* @nodoc
*/
onChange = (_: any) => {};
/**
* The registered callback function called when a blur event occurs on the input element.
* @nodoc
*/
onTouched = () => {};
constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {
super();
}
/**
* Sets the "value" property on the input element.
* @nodoc
*/
writeValue(value: any): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'value', parseFloat(value));
this.setProperty('value', parseFloat(value));
}
/**
@@ -86,20 +69,4 @@ export class RangeValueAccessor extends BuiltInControlValueAccessor implements
fn(value == '' ? null : parseFloat(value));
};
}
/**
* Registers a function called when the control is touched.
* @nodoc
*/
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
/**
* Sets the "disabled" property on the range input element.
* @nodoc
*/
setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}
}
@@ -99,18 +99,6 @@ export class SelectControlValueAccessor extends BuiltInControlValueAccessor impl
/** @internal */
_idCounter: number = 0;
/**
* The registered callback function called when a change event occurs on the input element.
* @nodoc
*/
onChange = (_: any) => {};
/**
* The registered callback function called when a blur event occurs on the input element.
* @nodoc
*/
onTouched = () => {};
/**
* @description
* Tracks the option comparison algorithm for tracking identities when
@@ -126,10 +114,6 @@ export class SelectControlValueAccessor extends BuiltInControlValueAccessor impl
private _compareWith: (o1: any, o2: any) => boolean = Object.is;
constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {
super();
}
/**
* Sets the "value" property on the input element. The "selectedIndex"
* property is also set if an ID is provided on the option element.
@@ -139,10 +123,10 @@ export class SelectControlValueAccessor extends BuiltInControlValueAccessor impl
this.value = value;
const id: string|null = this._getOptionId(value);
if (id == null) {
this._renderer.setProperty(this._elementRef.nativeElement, 'selectedIndex', -1);
this.setProperty('selectedIndex', -1);
}
const valueString = _buildValueString(id, value);
this._renderer.setProperty(this._elementRef.nativeElement, 'value', valueString);
this.setProperty('value', valueString);
}
/**
@@ -156,22 +140,6 @@ export class SelectControlValueAccessor extends BuiltInControlValueAccessor impl
};
}
/**
* Registers a function called when the control is touched.
* @nodoc
*/
registerOnTouched(fn: () => any): void {
this.onTouched = fn;
}
/**
* Sets the "disabled" property on the select input element.
* @nodoc
*/
setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}
/** @internal */
_registerOption(): string {
return (this._idCounter++).toString();
@@ -95,18 +95,6 @@ export class SelectMultipleControlValueAccessor extends BuiltInControlValueAcces
/** @internal */
_idCounter: number = 0;
/**
* The registered callback function called when a change event occurs on the input element.
* @nodoc
*/
onChange = (_: any) => {};
/**
* The registered callback function called when a blur event occurs on the input element.
* @nodoc
*/
onTouched = () => {};
/**
* @description
* Tracks the option comparison algorithm for tracking identities when
@@ -122,10 +110,6 @@ export class SelectMultipleControlValueAccessor extends BuiltInControlValueAcces
private _compareWith: (o1: any, o2: any) => boolean = Object.is;
constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {
super();
}
/**
* Sets the "value" property on one or of more of the select's options.
* @nodoc
@@ -179,22 +163,6 @@ export class SelectMultipleControlValueAccessor extends BuiltInControlValueAcces
};
}
/**
* Registers a function called when the control is touched.
* @nodoc
*/
registerOnTouched(fn: () => any): void {
this.onTouched = fn;
}
/**
* Sets the "disabled" property on the select input element.
* @nodoc
*/
setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}
/** @internal */
_registerOption(value: ɵNgSelectMultipleOption): string {
const id: string = (this._idCounter++).toString();