Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(forms): Improve a very commonly viewed error message by adding a guide #47969

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 28 additions & 0 deletions aio/content/errors/NG1203.md
@@ -0,0 +1,28 @@
@name Missing value accessor
@category forms
@shortDescription You must register an `NgValueAccessor` with a custom form control

@description
For all custom form controls, you must register a value accessor.

Here's an example of how to provide one:

```typescript
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyInputField),
multi: true,
}
]
```

@debugging
As described above, your control was expected to have a value accessor, but was missing one. However, there are many different reasons this can happen in practice. Here's a listing of some known problems leading to this error.

1. If you **defined** a custom form control, did you remember to provide a value accessor?
1. Did you put `ngModel` on an element with no value, or an **invalid element** (e.g. `<div [(ngModel)]="foo">`)?
1. Are you using a custom form control declared inside an `NgModule`? if so, make sure you are **importing** the `NgModule`.
1. Are you using `ngModel` with a third-party custom form control? Check whether that control provides a value accessor. If not, use **`ngDefaultControl`** on the control's element.
1. Are you **testing** a custom form control? Be sure to configure your testbed to know about the control. You can do so with `Testbed.configureTestingModule`.
1. Are you using **Nx and Module Federation** with Webpack? Your `webpack.config.js` may require [extra configuration](https://github.com/angular/angular/issues/43821#issuecomment-1054845431) to ensure the forms package is shared.
2 changes: 2 additions & 0 deletions goldens/public-api/forms/errors.md
Expand Up @@ -25,6 +25,8 @@ export const enum RuntimeErrorCode {
// (undocumented)
NAME_AND_FORM_CONTROL_NAME_MUST_MATCH = 1202,
// (undocumented)
NG_MISSING_VALUE_ACCESSOR = -1203,
// (undocumented)
NG_VALUE_ACCESSOR_NOT_PROVIDED = 1200,
// (undocumented)
NGMODEL_IN_FORM_GROUP = 1350,
Expand Down
4 changes: 2 additions & 2 deletions goldens/size-tracking/integration-payloads.json
Expand Up @@ -48,8 +48,8 @@
"animations": {
"uncompressed": {
"runtime": 1070,
"main": 155920,
"polyfills": 33814
"main": 156355,
dylhunn marked this conversation as resolved.
Show resolved Hide resolved
"polyfills": 33897
}
},
"standalone-bootstrap": {
Expand Down
8 changes: 7 additions & 1 deletion packages/forms/src/directives/shared.ts
Expand Up @@ -65,7 +65,7 @@ export function setUpControl(
callSetDisabledState: SetDisabledStateOption = setDisabledStateDefault): void {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (!control) _throwError(dir, 'Cannot find control with');
if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with');
if (!dir.valueAccessor) _throwMissingValueAccessorError(dir);
}

setUpValidators(control, dir);
Expand Down Expand Up @@ -322,6 +322,12 @@ function _describeControlLocation(dir: AbstractControlDirective): string {
return 'unspecified name attribute';
}

function _throwMissingValueAccessorError(dir: AbstractControlDirective) {
const loc = _describeControlLocation(dir);
throw new RuntimeError(
RuntimeErrorCode.NG_MISSING_VALUE_ACCESSOR, `No value accessor for form control ${loc}.`);
}

function _throwInvalidValueAccessorError(dir: AbstractControlDirective) {
const loc = _describeControlLocation(dir);
throw new RuntimeError(
Expand Down
1 change: 1 addition & 0 deletions packages/forms/src/errors.ts
Expand Up @@ -31,6 +31,7 @@ export const enum RuntimeErrorCode {
NG_VALUE_ACCESSOR_NOT_PROVIDED = 1200,
COMPAREWITH_NOT_A_FN = 1201,
NAME_AND_FORM_CONTROL_NAME_MUST_MATCH = 1202,
NG_MISSING_VALUE_ACCESSOR = -1203,

// Template-driven Forms errors (1350-1399)
NGMODEL_IN_FORM_GROUP = 1350,
Expand Down
12 changes: 7 additions & 5 deletions packages/forms/test/directives_spec.ts
Expand Up @@ -184,7 +184,8 @@ class CustomValidatorDirective implements Validator {
dir.name = 'login';

expect(() => form.addControl(dir))
.toThrowError(new RegExp(`No value accessor for form control with name: 'login'`));
.toThrowError(new RegExp(
`NG01203: No value accessor for form control name: 'login'. Find more at https://angular.io/errors/NG01203`));
});

it('should throw when no value accessor with path', () => {
Expand All @@ -195,7 +196,7 @@ class CustomValidatorDirective implements Validator {

expect(() => form.addControl(dir))
.toThrowError(new RegExp(
`No value accessor for form control with path: 'passwords -> password'`));
`NG01203: No value accessor for form control path: 'passwords -> password'. Find more at https://angular.io/errors/NG01203`));
});

it('should set up validators', fakeAsync(() => {
Expand Down Expand Up @@ -582,15 +583,16 @@ class CustomValidatorDirective implements Validator {
namedDir.name = 'one';

expect(() => namedDir.ngOnChanges({}))
.toThrowError(new RegExp(`No value accessor for form control with name: 'one'`));
.toThrowError(new RegExp(
`NG01203: No value accessor for form control name: 'one'. Find more at https://angular.io/errors/NG01203`));
});

it('should throw when no value accessor with unnamed control', () => {
const unnamedDir = new NgModel(null!, null!, null!, null!);

expect(() => unnamedDir.ngOnChanges({}))
.toThrowError(
new RegExp(`No value accessor for form control with unspecified name attribute`));
.toThrowError(new RegExp(
`NG01203: No value accessor for form control unspecified name attribute. Find more at https://angular.io/errors/NG01203`));
});

it('should set up validator', fakeAsync(() => {
Expand Down
4 changes: 3 additions & 1 deletion packages/forms/test/reactive_integration_spec.ts
Expand Up @@ -5093,7 +5093,9 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]');
const fixture = initTest(NoCVAComponent);
expect(() => {
fixture.detectChanges();
}).toThrowError('No value accessor for form control with name: \'control\'');
})
.toThrowError(
`NG01203: No value accessor for form control name: 'control'. Find more at https://angular.io/errors/NG01203`);

// Making sure that cleanup between tests doesn't cause any issues
// for not fully initialized controls.
Expand Down