Skip to content

Commit

Permalink
fix(forms): improve error message for invalid value accessors (#45192)
Browse files Browse the repository at this point in the history
improve error message for invalid value accessors when accessor is not provided as array

PR Close #45192
  • Loading branch information
ameryousuf authored and atscott committed Mar 15, 2022
1 parent 2d51587 commit 7ff4c0b
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 16 deletions.
34 changes: 19 additions & 15 deletions packages/forms/src/directives/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,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!];
}
Expand Down Expand Up @@ -88,7 +87,7 @@ export function cleanUpControl(
}

function registerOnValidatorChange<V>(validators: (V|Validator)[], onChange: () => void): void {
validators.forEach((validator: (V|Validator)) => {
validators.forEach((validator: V|Validator) => {
if ((<Validator>validator).registerOnValidatorChange)
(<Validator>validator).registerOnValidatorChange!(onChange);
});
Expand Down Expand Up @@ -169,7 +168,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);
Expand All @@ -182,7 +181,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);
Expand Down Expand Up @@ -273,17 +272,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'];
Expand Down Expand Up @@ -315,7 +321,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;
Expand All @@ -324,12 +330,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');
Expand Down
3 changes: 2 additions & 1 deletion packages/forms/test/directives_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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', () => {
Expand Down

0 comments on commit 7ff4c0b

Please sign in to comment.