Skip to content

Commit

Permalink
fix(forms): Update a Forms validator error to use RuntimeError (#46537)
Browse files Browse the repository at this point in the history
Replace `new Error()` in a forms Validators function with `RuntimeError`, for better tree-shakability. Also, improve the error messages, and add documentation.

PR Close #46537
  • Loading branch information
dylhunn committed Jun 28, 2022
1 parent 35f14c6 commit 7478722
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 13 deletions.
30 changes: 30 additions & 0 deletions aio/content/errors/NG1003.md
@@ -0,0 +1,30 @@
@name Wrong Async Validator Return Type
@category forms
@shortDescription Async validator must return a Promise or Observable

@description
Async validators must return a promise or an observable, and emit/resolve them whether the validation fails or succeeds. In particular, they must implement the [AsyncValidatorFn API](api/forms/AsyncValidator)

```typescript
export function isTenAsync(control: AbstractControl):
Observable<ValidationErrors> | null {
const v: number = control.value;
if (v !== 10) {
// Emit an object with a validation error.
return of({ 'notTen': true, 'requiredValue': 10 });
}
// Emit null, to indicate no error occurred.
return of(null);
}
```

@debugging
Did you mistakenly use a synchronous validator instead of an async validator?

<!-- links -->

<!-- external links -->

<!-- end links -->

@reviewed 2022-06-28
4 changes: 3 additions & 1 deletion goldens/public-api/forms/errors.md
Expand Up @@ -11,7 +11,9 @@ export const enum RuntimeErrorCode {
// (undocumented)
MISSING_CONTROL_VALUE = 1002,
// (undocumented)
NO_CONTROLS = 1000
NO_CONTROLS = 1000,
// (undocumented)
WRONG_VALIDATOR_RETURN_TYPE = -1101
}

// (No @packageDocumentation comment for this package)
Expand Down
Expand Up @@ -1133,9 +1133,6 @@
{
"name": "isObject"
},
{
"name": "isObservable"
},
{
"name": "isOptionsObj"
},
Expand Down
Expand Up @@ -1094,9 +1094,6 @@
{
"name": "isObject"
},
{
"name": "isObservable"
},
{
"name": "isOptionsObj"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/forms/src/errors.ts
Expand Up @@ -18,5 +18,8 @@ export const enum RuntimeErrorCode {
MISSING_CONTROL = 1001,
MISSING_CONTROL_VALUE = 1002,

// Validators errors
WRONG_VALIDATOR_RETURN_TYPE = -1101,

// Template-driven Forms errors (11xx)
}
19 changes: 14 additions & 5 deletions packages/forms/src/validators.ts
Expand Up @@ -6,13 +6,16 @@
* found in the LICENSE file at https://angular.io/license
*/

import {InjectionToken, ɵisObservable as isObservable, ɵisPromise as isPromise} from '@angular/core';
import {InjectionToken, ɵisObservable as isObservable, ɵisPromise as isPromise, ɵRuntimeError as RuntimeError} from '@angular/core';
import {forkJoin, from, Observable} from 'rxjs';
import {map} from 'rxjs/operators';

import {AsyncValidator, AsyncValidatorFn, ValidationErrors, Validator, ValidatorFn} from './directives/validators';
import {RuntimeErrorCode} from './errors';
import {AbstractControl} from './model/abstract_model';

const NG_DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;

function isEmptyInputValue(value: any): boolean {
/**
* Check if the object is a string or array before evaluating the length attribute.
Expand Down Expand Up @@ -567,10 +570,16 @@ function isPresent(o: any): boolean {
return o != null;
}

export function toObservable(r: any): Observable<any> {
const obs = isPromise(r) ? from(r) : r;
if (!(isObservable(obs)) && (typeof ngDevMode === 'undefined' || ngDevMode)) {
throw new Error(`Expected validator to return Promise or Observable.`);
export function toObservable(value: any): Observable<any> {
const obs = isPromise(value) ? from(value) : value;
if (NG_DEV_MODE && !(isObservable(obs))) {
let errorMessage = `Expected async validator to return Promise or Observable.`;
// A synchronous validator will return object or null.
if (typeof value === 'object') {
errorMessage +=
' Are you using a synchronous validator where an async validator is expected?';
}
throw new RuntimeError(RuntimeErrorCode.WRONG_VALIDATOR_RETURN_TYPE, errorMessage);
}
return obs;
}
Expand Down
5 changes: 4 additions & 1 deletion packages/forms/test/form_control_spec.ts
Expand Up @@ -1467,7 +1467,10 @@ describe('FormControl', () => {
// test for the specific error since without the error check it would still throw an error
// but
// not a meaningful one
expect(fn).toThrowError(`Expected validator to return Promise or Observable.`);
expect(fn).toThrowError(
'NG01101: Expected async validator to return Promise or Observable. ' +
'Are you using a synchronous validator where an async validator is expected? ' +
'Find more at https://angular.io/errors/NG01101');
});

it('should not emit value change events when emitEvent = false', () => {
Expand Down

0 comments on commit 7478722

Please sign in to comment.