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

View File

@ -112,13 +112,7 @@ export declare interface AsyncValidatorFn {
(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null>;
}
export declare class CheckboxControlValueAccessor extends ɵangular_packages_forms_forms_f implements ControlValueAccessor {
onChange: (_: any) => void;
onTouched: () => void;
constructor(_renderer: Renderer2, _elementRef: ElementRef);
registerOnChange(fn: (_: any) => {}): void;
registerOnTouched(fn: () => {}): void;
setDisabledState(isDisabled: boolean): void;
export declare class CheckboxControlValueAccessor extends ɵangular_packages_forms_forms_g implements ControlValueAccessor {
writeValue(value: any): void;
}
@ -141,13 +135,8 @@ export declare interface ControlValueAccessor {
writeValue(obj: any): void;
}
export declare class DefaultValueAccessor implements ControlValueAccessor {
onChange: (_: any) => void;
onTouched: () => void;
constructor(_renderer: Renderer2, _elementRef: ElementRef, _compositionMode: boolean);
registerOnChange(fn: (_: any) => void): void;
registerOnTouched(fn: () => void): void;
setDisabledState(isDisabled: boolean): void;
export declare class DefaultValueAccessor extends ɵangular_packages_forms_forms_f implements ControlValueAccessor {
constructor(renderer: Renderer2, elementRef: ElementRef, _compositionMode: boolean);
writeValue(value: any): void;
}
@ -382,11 +371,11 @@ export declare abstract class NgControl extends AbstractControlDirective {
abstract viewToModelUpdate(newValue: any): void;
}
export declare class NgControlStatus extends ɵangular_packages_forms_forms_h {
export declare class NgControlStatus extends ɵangular_packages_forms_forms_i {
constructor(cd: NgControl);
}
export declare class NgControlStatusGroup extends ɵangular_packages_forms_forms_h {
export declare class NgControlStatusGroup extends ɵangular_packages_forms_forms_i {
constructor(cd: ControlContainer);
}
@ -454,13 +443,8 @@ export declare class NgSelectOption implements OnDestroy {
ngOnDestroy(): void;
}
export declare class NumberValueAccessor extends ɵangular_packages_forms_forms_f implements ControlValueAccessor {
onChange: (_: any) => void;
onTouched: () => void;
constructor(_renderer: Renderer2, _elementRef: ElementRef);
export declare class NumberValueAccessor extends ɵangular_packages_forms_forms_g implements ControlValueAccessor {
registerOnChange(fn: (_: number | null) => void): void;
registerOnTouched(fn: () => void): void;
setDisabledState(isDisabled: boolean): void;
writeValue(value: number): void;
}
@ -471,29 +455,21 @@ export declare class PatternValidator implements Validator, OnChanges {
validate(control: AbstractControl): ValidationErrors | null;
}
export declare class RadioControlValueAccessor extends ɵangular_packages_forms_forms_f implements ControlValueAccessor, OnDestroy, OnInit {
export declare class RadioControlValueAccessor extends ɵangular_packages_forms_forms_g implements ControlValueAccessor, OnDestroy, OnInit {
formControlName: string;
name: string;
onChange: () => void;
onTouched: () => void;
value: any;
constructor(_renderer: Renderer2, _elementRef: ElementRef, _registry: ɵangular_packages_forms_forms_p, _injector: Injector);
constructor(renderer: Renderer2, elementRef: ElementRef, _registry: ɵangular_packages_forms_forms_q, _injector: Injector);
fireUncheck(value: any): void;
ngOnDestroy(): void;
ngOnInit(): void;
registerOnChange(fn: (_: any) => {}): void;
registerOnTouched(fn: () => {}): void;
setDisabledState(isDisabled: boolean): void;
writeValue(value: any): void;
}
export declare class RangeValueAccessor extends ɵangular_packages_forms_forms_f implements ControlValueAccessor {
onChange: (_: any) => void;
onTouched: () => void;
constructor(_renderer: Renderer2, _elementRef: ElementRef);
export declare class RangeValueAccessor extends ɵangular_packages_forms_forms_g implements ControlValueAccessor {
registerOnChange(fn: (_: number | null) => void): void;
registerOnTouched(fn: () => void): void;
setDisabledState(isDisabled: boolean): void;
writeValue(value: any): void;
}
@ -509,27 +485,17 @@ export declare class RequiredValidator implements Validator {
validate(control: AbstractControl): ValidationErrors | null;
}
export declare class SelectControlValueAccessor extends ɵangular_packages_forms_forms_f implements ControlValueAccessor {
export declare class SelectControlValueAccessor extends ɵangular_packages_forms_forms_g implements ControlValueAccessor {
set compareWith(fn: (o1: any, o2: any) => boolean);
onChange: (_: any) => void;
onTouched: () => void;
value: any;
constructor(_renderer: Renderer2, _elementRef: ElementRef);
registerOnChange(fn: (value: any) => any): void;
registerOnTouched(fn: () => any): void;
setDisabledState(isDisabled: boolean): void;
writeValue(value: any): void;
}
export declare class SelectMultipleControlValueAccessor extends ɵangular_packages_forms_forms_f implements ControlValueAccessor {
export declare class SelectMultipleControlValueAccessor extends ɵangular_packages_forms_forms_g implements ControlValueAccessor {
set compareWith(fn: (o1: any, o2: any) => boolean);
onChange: (_: any) => void;
onTouched: () => void;
value: any;
constructor(_renderer: Renderer2, _elementRef: ElementRef);
registerOnChange(fn: (value: any) => any): void;
registerOnTouched(fn: () => any): void;
setDisabledState(isDisabled: boolean): void;
writeValue(value: any): void;
}

View File

@ -44,6 +44,9 @@
{
"name": "BROWSER_MODULE_PROVIDERS"
},
{
"name": "BaseControlValueAccessor"
},
{
"name": "BrowserDomAdapter"
},
@ -1637,6 +1640,9 @@
{
"name": "ɵɵelementStart"
},
{
"name": "ɵɵgetInheritedFactory"
},
{
"name": "ɵɵinject"
},

View File

@ -44,6 +44,9 @@
{
"name": "BROWSER_MODULE_PROVIDERS"
},
{
"name": "BaseControlValueAccessor"
},
{
"name": "BrowserDomAdapter"
},
@ -1616,6 +1619,9 @@
{
"name": "ɵɵelementStart"
},
{
"name": "ɵɵgetInheritedFactory"
},
{
"name": "ɵɵinject"
},

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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 */

View File

@ -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);
}
}

View File

@ -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)) {

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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();