Skip to content

Latest commit

 

History

History
194 lines (151 loc) · 8.53 KB

FORM_CONTROLS.md

File metadata and controls

194 lines (151 loc) · 8.53 KB

Form Controls

Form controls in ngrx-forms are represented as plain state objects. Control states have the following shape:

export type FormControlValueTypes = string | number | boolean | null | undefined;
export interface KeyValue { [key: string]: any; }
export interface ValidationErrors { [key: string]: any; }

export interface AbstractControlState<TValue> {
  id: string;
  value: TValue;
  isValid: boolean;
  isInvalid: boolean;
  errors: ValidationErrors;
  pendingValidations: string[];
  isValidationPending: boolean;
  isEnabled: boolean;
  isDisabled: boolean;
  isDirty: boolean;
  isPristine: boolean;
  isTouched: boolean;
  isUntouched: boolean;
  isSubmitted: boolean;
  isUnsubmitted: boolean;
  userDefinedProperties: KeyValue;
}

export interface FormControlState<TValue extends FormControlValueTypes> extends AbstractControlState<TValue> {
  isFocused: boolean;
  isUnfocused: boolean;
}

The following table explains each property.

Property Negated Description
id The unique ID of the form control. Usually this is the name of the field in the form value prefixed by the ID of the containing group or array, e.g. MY_FORM.someTextInput.
value The value of the form control. Controls only support values of type string, number, boolean, null, and undefined to keep the state string serializable.
isValid isInvalid The isValid property is true if the control does not have any errors.
errors The errors of the control. This property always has a value. If the control has no errors the property is set to {}.
pendingValidations The names of all asynchronous validations currently running for the control.
isValidationPending The isValidationPending property indicates whether the control is currently being asynchronously validated (i.e. this is true if and only if pendingValidations is not empty).
isEnabled isDisabled The isEnabled property indicates whether the control is enabled. When isEnabled is false the errors are always {} (i.e. the control is always valid if disabled) and pendingValidations is always [] (i.e. all pending validations are cancelled).
isDirty isPristine The isDirty property is set to true as soon as the the underlying FormViewAdapter or ControlValueAccessor reports a new value for the first time.
isTouched isUntouched The isTouched property is set to true based on the rules of the underlying FormViewAdapter or ControlValueAccessor (usually on blur for most form elements).
isSubmitted isUnsubmitted The isSubmitted property is set to true if the containing group or array is submitted.
isFocused isUnfocused The isFocused property is set to true if the control currently has focus. Note that this feature is opt-in. To enable it you have to add [ngrxEnableFocusTracking]="true" to your form element.
userDefinedProperties Sometimes it is useful to associate your own metadata with a form control (e.g. if you wanted to count the number of times a control's value has been changed, what keys were pressed on an input, or how often a form has been submitted). While it is possible to store this kind of information outside of ngrx-forms in your own state the userDefinedProperties allow you to store your own metadata directly in a control's state.

Control states are associated with a form element via the NgrxFormControlDirective (applied with [ngrxFormControlState]="controlState"). This directive is reponsible for keeping the view and the state in sync. When the state is changed the update is always immediately sync'ed to the view. Additionally the id of the HTML element is set to the ID of the form control.

Status CSS Classes

ngrx-forms adds CSS classes to form control elements depending on the state of the control. The available classes are:

  • ngrx-forms-valid
  • ngrx-forms-invalid
  • ngrx-forms-dirty
  • ngrx-forms-pristine
  • ngrx-forms-touched
  • ngrx-forms-untouched
  • ngrx-forms-submitted
  • ngrx-forms-unsubmitted
  • ngrx-forms-validation-pending

A constant NGRX_STATUS_CLASS_NAMES is exported to allow accessing these class names in user code without needing to hard-code them.

ngrxUpdateOn

It is possible to control when view values changes are pushed to the state with the ngrxUpdateOn attribute. The supported values are change (pushed immediately when the view value changes; default) and blur (pushed when the form element loses focus). Note that by changing the time value changes are pushed to the state you are also changing the time at which validation and other state updates that depend on the value happen.

User Defined Properties

As mentioned in the section about properties of form controls it is possible to store additional metadata on a control. The following is an example of a directive that is applied to all text inputs and tracks whether the ENTER key is currently being pressed on the input. This data can then be used for example in an effect to trigger a validation or server call (e.g. for an autocomplete) if the user presses enter by reacting to the custom property changing from false to true.

import { Directive, HostListener, Input } from '@angular/core';
import { ActionsSubject } from '@ngrx/store';
import { FormControlState, SetUserDefinedPropertyAction } from 'ngrx-forms';

export const IS_ENTER_PRESSED_PROPERTY = 'isEnterPressed';
export const ENTER_KEY_CODE = 13;

@Directive({
  selector: 'input[type=text][ngrxFormControlState]',
})
export class TrackIsEnterPressedDirective {
  @Input() ngrxFormControlState: FormControlState<string>;

  constructor(private actionsSubject: ActionsSubject) { }

  @HostListener('keydown', ['$event'])
  onKeyDown(event: KeyboardEvent) {
    if (event.keyCode !== ENTER_KEY_CODE) {
      return;
    }

    this.actionsSubject.next(new SetUserDefinedPropertyAction(this.ngrxFormControlState.id, IS_ENTER_PRESSED_PROPERTY, true));
  }

  @HostListener('keyup', ['$event'])
  onKeyUp(event: KeyboardEvent) {
    if (event.keyCode !== ENTER_KEY_CODE) {
      return;
    }

    this.actionsSubject.next(new SetUserDefinedPropertyAction(this.ngrxFormControlState.id, IS_ENTER_PRESSED_PROPERTY, false));
  }
}

Value Conversion

If you need to use a form element that only supports objects as values (e.g. most custom date picker and tag input components) you can provide a value converter via the ngrxValueConverter attribute to perform a conversion between view and state values. Value converters are simple objects with two functions:

export interface NgrxValueConverter<TView, TState> {
  convertViewToStateValue(value: TView): TState;
  convertStateToViewValue(value: TState): TView;
}

ngrx-forms ships with a number of pre-made value converters:

Converter Description
dateToISOString Converts Date values to ISO date strings (and vice versa)
objectToJSON Converts any object to a JSON string via JSON.stringify (and vice versa via JSON.parse)

Below you can find a full example on how to use a value converter to work with dates as view values:

import { Action } from '@ngrx/store';
import { FormGroupState, createFormGroupState, formGroupReducer } from 'ngrx-forms';

export interface MyFormValue {
  date: string;
}

const FORM_ID = 'some globally unique string';

const initialFormState = createFormGroupState<MyFormValue>(FORM_ID, {
  date: new Date(0).toISOString(),
});

export interface AppState {
  myForm: FormGroupState<MyFormValue>;
}

const initialState: AppState = {
  myForm: initialFormState,
};

export function appReducer(state = initialState, action: Action): AppState {
  const myForm = formGroupReducer(state.myForm, action);
  if (myForm !== state.myForm) {
    return { ...state, myForm };
  }

  return state;
}
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { FormGroupState, NgrxValueConverters } from 'ngrx-forms';
import { Observable } from 'rxjs/Observable';

import { MyFormValue } from './reducer';

@Component({
  selector: 'my-component',
  templateUrl: './my-component.html',
})
export class MyComponent {
  formState$: Observable<FormGroupState<MyFormValue>>;

  constructor(private store: Store<AppState>) {
    this.formState$ = store.select(s => s.myForm);
  }

  dateValueConverter = NgrxValueConverters.dateToISOString;
}
<form novalidate [ngrxFormState]="(formState$ | async)">
  <custom-date-picker [ngrxFormControlState]="(formState$ | async).controls.date"
                      [ngrxValueConverter]="dateValueConverter"></custom-date-picker>
</form>