diff --git a/packages/forms/src/directives/shared.ts b/packages/forms/src/directives/shared.ts index 377965f492ac..3915e16b893d 100644 --- a/packages/forms/src/directives/shared.ts +++ b/packages/forms/src/directives/shared.ts @@ -22,7 +22,6 @@ import {FormArrayName} from './reactive_directives/form_group_name'; import {ngModelWarning} from './reactive_errors'; import {AsyncValidatorFn, Validator, ValidatorFn} from './validators'; - export function controlPath(name: string|null, parent: ControlContainer): string[] { return [...parent.path!, name!]; } @@ -91,7 +90,7 @@ export function cleanUpControl( } function registerOnValidatorChange(validators: (V|Validator)[], onChange: () => void): void { - validators.forEach((validator: (V|Validator)) => { + validators.forEach((validator: V|Validator) => { if ((validator).registerOnValidatorChange) (validator).registerOnValidatorChange!(onChange); }); @@ -172,7 +171,7 @@ export function cleanUpValidators( const validators = getControlValidators(control); if (Array.isArray(validators) && validators.length > 0) { // Filter out directive validator function. - const updatedValidators = validators.filter(validator => validator !== dir.validator); + const updatedValidators = validators.filter((validator) => validator !== dir.validator); if (updatedValidators.length !== validators.length) { isControlUpdated = true; control.setValidators(updatedValidators); @@ -185,7 +184,7 @@ export function cleanUpValidators( if (Array.isArray(asyncValidators) && asyncValidators.length > 0) { // Filter out directive async validator function. const updatedAsyncValidators = - asyncValidators.filter(asyncValidator => asyncValidator !== dir.asyncValidator); + asyncValidators.filter((asyncValidator) => asyncValidator !== dir.asyncValidator); if (updatedAsyncValidators.length !== asyncValidators.length) { isControlUpdated = true; control.setAsyncValidators(updatedAsyncValidators); @@ -276,17 +275,24 @@ function _noControlError(dir: NgControl) { } function _throwError(dir: AbstractControlDirective, message: string): void { - let messageEnd: string; - if (dir.path!.length > 1) { - messageEnd = `path: '${dir.path!.join(' -> ')}'`; - } else if (dir.path![0]) { - messageEnd = `name: '${dir.path}'`; - } else { - messageEnd = 'unspecified name attribute'; - } + const messageEnd = _describeControlLocation(dir); throw new Error(`${message} ${messageEnd}`); } +function _describeControlLocation(dir: AbstractControlDirective): string { + const path = dir.path; + if (path && path.length > 1) return `path: '${path.join(' -> ')}'`; + if (path?.[0]) return `name: '${path}'`; + return 'unspecified name attribute'; +} + +function _throwInvalidValueAccessorError(dir: AbstractControlDirective) { + const loc = _describeControlLocation(dir); + throw new Error( + `Value accessor was not provided as an array for form control with ${loc}. ` + + `Check that the \`NG_VALUE_ACCESSOR\` token is configured as a \`multi: true\` provider.`); +} + export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any): boolean { if (!changes.hasOwnProperty('model')) return false; const change = changes['model']; @@ -318,7 +324,7 @@ export function selectValueAccessor( if (!valueAccessors) return null; if (!Array.isArray(valueAccessors) && (typeof ngDevMode === 'undefined' || ngDevMode)) - _throwError(dir, 'Value accessor was not provided as an array for form control with'); + _throwInvalidValueAccessorError(dir); let defaultAccessor: ControlValueAccessor|undefined = undefined; let builtinAccessor: ControlValueAccessor|undefined = undefined; @@ -327,12 +333,10 @@ export function selectValueAccessor( valueAccessors.forEach((v: ControlValueAccessor) => { if (v.constructor === DefaultValueAccessor) { defaultAccessor = v; - } else if (isBuiltInAccessor(v)) { if (builtinAccessor && (typeof ngDevMode === 'undefined' || ngDevMode)) _throwError(dir, 'More than one built-in value accessor matches form control with'); builtinAccessor = v; - } else { if (customAccessor && (typeof ngDevMode === 'undefined' || ngDevMode)) _throwError(dir, 'More than one custom value accessor matches form control with'); diff --git a/packages/forms/test/directives_spec.ts b/packages/forms/test/directives_spec.ts index 0b94bdc02bdf..90d138de139c 100644 --- a/packages/forms/test/directives_spec.ts +++ b/packages/forms/test/directives_spec.ts @@ -11,6 +11,7 @@ import {fakeAsync, flushMicrotasks, tick} from '@angular/core/testing'; import {AbstractControl, CheckboxControlValueAccessor, ControlValueAccessor, DefaultValueAccessor, FormArray, FormArrayName, FormControl, FormControlDirective, FormControlName, FormGroup, FormGroupDirective, FormGroupName, NgControl, NgForm, NgModel, NgModelGroup, SelectControlValueAccessor, SelectMultipleControlValueAccessor, ValidationErrors, Validator, Validators} from '@angular/forms'; import {selectValueAccessor} from '@angular/forms/src/directives/shared'; import {composeValidators} from '@angular/forms/src/validators'; + import {asyncValidator} from './util'; class DummyControlValueAccessor implements ControlValueAccessor { @@ -53,7 +54,7 @@ class CustomValidatorDirective implements Validator { it('should throw when accessor is not provided as array', () => { expect(() => selectValueAccessor(dir, {} as any[])) .toThrowError( - `Value accessor was not provided as an array for form control with unspecified name attribute`); + 'Value accessor was not provided as an array for form control with unspecified name attribute. Check that the \`NG_VALUE_ACCESSOR\` token is configured as a \`multi: true\` provider.'); }); it('should return the default value accessor when no other provided', () => {