Skip to content

Latest commit

 

History

History
139 lines (115 loc) · 6.41 KB

VALIDATION.md

File metadata and controls

139 lines (115 loc) · 6.41 KB

Validation

As mentioned in the section about updating the state the validate update function takes a function as a parameter that validates the value of a form control or group. ngrx-forms provides a set of validation functions out of the box (imported via ngrx-forms/validation) that can be used as arguments to validate. Most of these functions treat null (and for email and pattern empty strings) as valid to allow for optional form controls. If the control is not optional simply combine the corresponding validation function with the required validation function.

Function Description
required Requires the value to be non-empty (i.e. non-null, non-empty string, non-empty array etc.)
requiredTrue Requires the boolean value to be true
requiredFalse Requires the boolean value to be false
equalTo Requires the value to be equal to another value
lessThan Requires the number value to be less than another number
lessThanOrEqualTo Requires the number value to be less than or equal to another number
greaterThan Requires the number value to be greater than another number
greaterThanOrEqualTo Requires the number value to be greater than or equal to another number
minLength Requires a string or array value to have a minimum length
maxLength Requires a string or array value to have a maximum length
email Requires a string value to be a valid e-mail address
pattern Requires a string value to match a regular expression

Below you can see an example of how these functions can be used:

import { updateGroup, validate } from 'ngrx-forms';
import { required, greaterThanOrEqualTo, lessThan } from 'ngrx-forms/validation';

export interface NestedValue {
  someNumber: number;
}

export interface MyFormValue {
  someTextInput: string;
  someCheckbox: boolean;
  nested: NestedValue;
}

const updateMyFormGroup = updateGroup<MyFormValue>({
  someTextInput: validate(required),
  nested: updateGroup({
    someNumber: validate([required, greaterThanOrEqualTo(2), lessThan(10)]),
  }),
});

Asynchronous Validation

In addition to the synchronous validation via update functions explained above ngrx-forms supports asynchronous validation for form elements. However, since asynchronous validations are by nature not side-effect free they need to be handled differently.

ngrx-forms provides a set of three actions that can be used to perform asynchronous validation. These actions can be dispatched however you like, be that from a service or from within effects. The first of these actions is the StartAsyncValidationAction which takes the name of the validation to be performed. This name is added to the pendingValidations of the control state and the isValidationPending flag is set to true (if it was not already) for the control and all its parents. However, the validity of the control is not affected by this action. This means, if you e.g. want to disable the submit button of your form while the form is invalid or currently validating you need to check two properties, e.g. [disabled]="formState.isInvalid || formState.isValidationPending". You can have as many asynchronous validations running at the same time as you like. The isValidationPending flag will be true as long as at least one validation has not yet completed.

The last two actions are used to complete the validation. The SetAsyncErrorAction takes the name of the validation and an arbitrary value and adds an error with the given name (prefixed with a $) and value to the state's errors. It also removes the validation from the control's pendingValidations. The $ prefix marks all asynchronous errors which allows the synchronous validation via update functions to preserve these errors. That means you can safely combine synchronous validation and asynchronous validation. By adding the error the control will be marked as invalid if it was not already. The other action is the ClearAsyncErrorAction which takes only the name of the validation and removes the error if it was present. This action also removes the validation from the control's pendingValidations.

Below you can find an example of the steps that occur during such an asynchronous validation. Each step shows a slice of the control's state at the time. The scenario is a book search in a book store.

  1. The user types a search
{
  "value": "some book I am looking for",
  "isValid": true,
  "isInvalid": false,
  "errors": {},
  "pendingValidations": [],
  "isValidationPending": false
}
  1. Your code dispatches a StartAsyncValidationAction for the name "exists"
{
  "value": "some book I am looking for",
  "isValid": true,
  "isInvalid": false,
  "errors": {},
  "pendingValidations": ["exists"],
  "isValidationPending": true
}
  1. The search returns that the book does not exist, i.e. your code dispatches a SetAsyncErrorAction for "exists" with value true (this value can be freely chosen and only exists so that you may use it to store metadata that you want to attach to the error)
{
  "value": "some book I am looking for",
  "isValid": false,
  "isInvalid": true,
  "errors": {
    "$exists": true,
  },
  "pendingValidations": [],
  "isValidationPending": false
}
  1. The user types another search and your code dispatches another StartAsyncValidationAction for the name "exists"
{
  "value": "lord of the rings",
  "isValid": false,
  "isInvalid": true,
  "errors": {
    "$exists": true,
  },
  "pendingValidations": ["exists"],
  "isValidationPending": true
}
  1. The search returns that the book does exist, so your code dispatches a ClearAsyncErrorAction for "exists"
{
  "value": "lord of the rings",
  "isValid": true,
  "isInvalid": false,
  "errors": {},
  "pendingValidations": [],
  "isValidationPending": false
}

If you are using @ngrx/effects your validation might look like this:

@Effect()
validateBookExists$: Observable<Action> = this.actions$
  .ofType(StartBookSearchAction.TYPE)
  .switchMap(a =>
    this.http.get(`api/books/search/${a.searchTerm}`)
      .map(resp =>
        resp.status === 404
          ? new SetAsyncErrorAction(a.controlId, "exists", true)
          : new ClearAsyncErrorAction(a.controlId, "exists")
      )
      // controlId may either be sent with the action or obtained from the store via withLatestFrom
      .startWith(new StartAsyncValidationAction(a.controlId, "exists"))
  );