Skip to content

Field ​

Field<T> represents a single typed form value. It extends FieldBase, which defines every member of a field; this page describes what Field implements. It is constructed with new, like Action, Group and List, and every read through it (field.value, field.valid, field.errors) is tracked by Vue.

The model describes how the parts of the library fit together.

Creating a field ​

typescript
import { Field } from '@dynamicforms/vue-forms';

const name = new Field({ value: 'John' });
const age  = new Field<number>({ value: 30 });

new Field<T>(params?) ​

params is an IFieldParams<T, X> and is optional. It contains the members below, plus the extended properties the field's second type argument declares. access, actions, errors, touched, validators and visibility are the parameters every element takes; see Constructor parameters, which also describes the order in which they are applied and which parameters are rejected.

ParameterTypeDefault

params.originalValue

T

default same as value

Baseline for isChanged, and the initial value when no value is given

params.value

T

default undefined

Initial value. Leaving it out, or passing undefined, falls back to originalValue; an explicit null is kept as the value

The first generic argument is inferred from params.value, so new Field({ value: 'John' }) is a Field<string> and new Field() is a Field<any>. Pass it explicitly when the initial value does not pin the type you want: new Field<number | null>({ value: null }). The second type argument is never inferred; see extended properties.

Properties ​

PropertyTypeWritable

touched

boolean

writable

Overrides FieldBase.touched: the field stores the flag and returns the value last assigned, or params.touched. Assigning true removes the errors whose rule removes them on 'edit', as described there

value

T

writable

Overrides FieldBase.value: the single value the field holds. A write is accepted whatever the access; the value the field ends up holding depends on what is registered on the field. Values are compared by identity, with NaN equal to NaN, so ValueChangedAction fires for a new object even when it is deeply equal to the old one, and does not fire when the field is assigned the object it already holds. Assign a modified copy instead of writing into the object the field holds: such a write is not a change of the field, and where originalValue is the same object it changes the baseline as well (The model). isChanged is separate and uses deep equality.

Writing the value ​

The value a field holds after a write to value depends on the actions registered on it. A ValueChangedAction may write another value back (a rule that trims, rounds or caps), and a handler that throws reverts the whole write and rethrows, so the field keeps its previous value. The write itself is not intercepted: there is no ValueChangingAction corresponding to AccessChangingAction and VisibilityChangingAction; ValueChangedAction runs after the value is stored. Inside an open transaction() the handlers run at the commit, so the final value is known once the outermost transaction() call returns.

Read the field back to get the value it holds:

typescript
field.value = typed;
if (field.value !== typed) {
  // the field holds something other than what was written
}

What a rendering layer sees ​

Where the field ends up holding a value that differs from both the written value and its previous value, every read of it updates: a computed over field.value returns the new value, an effect that reads it re-runs, and a control rendering from either re-renders.

Where the field ends up holding the value it held before the write, no read updates. A write of the value the field already holds does not write the reactive slot. A rule that restores the previous five characters after a six-character write, and a handler that throws, both leave the slot holding its previous value. In each case a computed over field.value returns the same value as before, so nothing rendering through it re-runs; an effect reading the field directly re-runs where the slot was written and restored, and reads the same value both times.

The control already shows what was typed: a native input holds the text in its own DOM before the input event is dispatched to the field. Since no read updates, the control is not re-rendered and shows a value the field does not hold until an unrelated change re-renders it. In the example, the field holds five characters and the input shows six.

A binding layer must handle this case itself. The read the control renders from must update even where the field did not, by returning the written value for one tick and the field's value from the next one:

typescript
import { computed, nextTick, shallowRef } from 'vue';

// inside a composable holding the element as field: FieldBase<T>
const pending = shallowRef<{ value: T } | null>(null);

const model = computed<T>({
  get: () => (pending.value ? pending.value.value : field.value),
  set: (newValue: T) => {
    try {
      field.value = newValue;
    } finally {
      if (field.value !== newValue) {
        pending.value = { value: newValue };
        nextTick(() => {
          pending.value = null;
        });
      }
    }
  },
});

Wherever the field holds something other than what was written (another value, or the value it already held), the read returns the written value for the rest of that tick and the field's value from the next one. The second change re-renders the control, so it shows what the field holds whether or not the field's value changed. The finally covers a handler that throws: the exception propagates to the caller and the read is still corrected. @dynamicforms/vuetify-inputs implements this in useInputBase(), which every input in that library uses.

Methods ​

bind(data?, overrides?): this ​

Overrides FieldBase.bind(), which describes overrides, the copied actions and extended properties, the construction through this.constructor and the detached result. The new field holds data, or this field's value where data is undefined, and is typed as the class bind() is called on.

Inherited members ​

Inherited from FieldBase
MemberDescription
accessWhether the element accepts input, what it sends to its container, and whether it is validated
bindingsOf(declaration)Returns every element in the subtree whose declaration is the one given
busytrue while an Action.execute() at or below the element has not settled
clearValidators()Removes the element's validators and empties errors
contributionWhat the element sends to its container's value
declarationThe element this one was declared as: itself, or the element a binding was made from
effectiveAccessThe access that applies once the containers above are taken into account
effectiveEnabledtrue where effectiveAccess is 'editable'
enabledtrue where access is 'editable'
errorsCurrent validation errors of the element; writable
extraThe extended properties the element holds, frozen; written through setExtendedValues()
fieldNameKey name within the parent Group
fullValueWhat the element holds; identical to value except on Group and List
isChangedtrue when value differs from originalValue (deep equality)
markRecordIncomplete()Marks that an eager action did not find a second element of a record not yet assembled
originalValueBaseline for isChanged; writable
parentContainer the element belongs to
pendingvalidating || busy
rebind(data)Replaces the data the element holds, in place
registerAction(action)Registers an action on the element; returns this
registerActionBefore(action, before)Registers action inside an existing chain, wrapped by before
setExtendedValues(values)Merges extended properties into extra
settled()Resolves once pending is false
triggerAction(actionClass, ...params)Fires an action class on the element and returns what the chain returns
unregisterAction(action)Removes an action from the element's declaration and its bindings
validtrue when errors is empty
validate(revalidate?)Publishes the validity derived from errors
validatingtrue while an asynchronous validation is in flight on the element or below it
visibilityHow a rendering layer shows the element; writable

NullableField<T> ​

Type alias for Field<T> | null.


See also: FieldBase, Basic Form example, Validators example

Released under the MIT License.