RelatedViewModelField

Source
import { RelatedViewModelField } from "@prestojs/viewmodel";

Define a field that references another ViewModel

This requires two things:

1) The ViewModel to reference 2) The field on the source ViewModel that contains the ID for the relation

In the following example User has a Group as a relation. The id for the connected group is stored on the groupId field:

class Group extends viewModelFactory({
name: new CharField(),
}) {}
class User extends viewModelFactory({
name: new CharField(),
groupId: new IntegerField(),
group: new RelatedViewModelField({
to: Group,
sourceFieldName: 'groupId',
}),
}) {}

You can then fetch the data - including the relations - from the cache:

Group.cache.add({ id: 1, name: 'Staff' });
User.cache.add({ id: 1, name: 'Bob', groupId: 1 });
User.cache.get(['name', 'group']);
// { id: 1, name: 'Bob', groupId: 1, group: { id: 1, name: 'Staff' }}

The to field can also be a function to support circular references:

class Group extends viewModelFactory({
name: new CharField(),
ownerId: new IntegerField(),
owner: new RelatedViewModelField({
to: () => User,
sourceFieldName: 'ownerId',
}),
}) {}
class User extends viewModelFactory({
name: new CharField(),
groupId: new IntegerField(),
group: new RelatedViewModelField({
to: Group,
sourceFieldName: 'groupId',
}),
}) {}

You can query the circular relations as deep as you want:

Group.cache.add({ id: 1, name: 'Staff', ownerId: 1 });
User.cache.add({ id: 1, name: 'Bob', groupId: 1 });
User.cache.get(['name', 'group', ['group', 'owner'], ['group', 'owner', 'group']);
// {
// id: 1,
// name: 'Bob',
// groupId: 1,
// group: {
// id: 1,
// name: 'Staff',
// ownerId: 1,
// owner: {
// id: 1,
// name: 'Bob',
// groupId: 1,
// group: {
// id: 1,
// name: 'Staff',
// ownerId: 1,
// }
// },
// },
// }

to can also be a a function that returns a Promise. This is useful to lazy load modules:

class Subscription extends viewModelFactory({
userId: new IntegerField(),
user: new RelatedViewModelField({
sourceFieldName: 'userId',
to: async () => {
const User = await import('./User').default;
return User;
}
})
}) {}

NOTE: When you return a promise you have to call resolveViewModel on that field before it's usable:

await Subscription.fields.user.resolveViewModel()

Failure to do this will result in an error being thrown the first time it's accessed.

If you have multiple values use ManyRelatedViewModelField instead.

API

new RelatedViewModelField(props)

Source
ParameterTypeDescription
props.asyncChoicesAsyncChoicesInterface

Asynchronous choices for this field.

Only one of asyncChoices and choices should be passed.

props.blankboolean

Is this field allowed to be assigned a blank (null, undefined, "") value?

Defaults to false

props.blankAsNullboolean

Frontend values are often stored as strings even if they are not stored like that in a backend (eg. database). Depending on your backend implementation it may expect empty values to be represented as null rather than an empty string. Setting blankAsNull to true indicates that empty strings should be converted to null when being sent to the backend.

props.choicesMap|[SingleValueT, string][]

Choices for this field. Should be a mapping of value to the label for the choice.

props.defaultValueBaseRelatedViewModelValueType|null|
Function

Default value for this field. This can either be a function that returns a value or the value directly.

props.helpTextstring

Optional help text for this field that might be shown on a form

props.labelstring

Label for this field. If not specified will be generated from the name.

props.readOnlyboolean

True if field should be considered read only (eg. excluded from forms)

props.writeOnlyboolean

True if field should be considered write only (eg. excluded from detail views)

*props.sourceFieldNamestring

The name of the field on the ViewModel that stores the ID for this relation

*props.to
Function
|ViewModel Class

Either a ViewModel, a function that returns a ViewModel or a function that returns a Promise that resolves to a ViewModel.

Methods

Converts a value into the relations ViewModel instance.

ParameterTypeDescription
*valueany
RelatedViewModelValueType

Converts the linked record to a plain javascript object

ParameterTypeDescription
*valueRelatedViewModelValueType
Record

Static Properties

Inherited Methods

Returns a clone of the field that should be functionally equivalent

Field

Format the value for displaying in a form widget. eg. This could convert a Date into a localized date string

ParameterTypeDescription
*valueRelatedViewModelValueType
any

Compares to relations for equality - if the ViewModel has the same data this returns true

ParameterTypeDescription
*value1RelatedViewModelValueType
*value2RelatedViewModelValueType
boolean

Parse a value received from a form widget onChange call. eg. This could convert a localized date string into a Date.

ParameterTypeDescription
*valueFieldDataMappingRaw|null

One of the following:

RelatedViewModelValueType

OR

null

Resolves the ViewModel this field links to. This is necessary as the ViewModel might be a dynamic import that hasn't yet loaded.

This needs to be called manually before to can be accessed.

Promise<ViewModel Class>
string

Inherited Properties

Async choices for this field.

Is this field required when saving a record?

If true an empty string value should be converted to a null value

When accessed on a bound field will return the current instance of the ViewModel the field is bound to.

If called on an unbound field then this will always be undefined and a warning will be raised.

Get the default value for this field.

Help text that can be displayed with the form widget

Returns true if field is bound to a ViewModel instance. When a field is bound to a instance the value for that field is accessible on the 'value' property.

Label that can be displayed as the form label for a widget

If not specified will be generated from name.

The ViewModel class this field is attached to.

This will throw an error if the field is not attached to a model.

The name of this field.

This will throw an error if the field is not attached to a model.

Indicates this field should only be read, not written. Not enforced but can be used by components to adjust their output accordingly (eg. exclude it from a form or show it on a form with a read only input)

Get the ViewModel this related field is to.

If to was defined as a function returning a Promise then you must call resolveViewModel and wait for the returned Promise to resolve before accessing this otherwise an error will be thrown

When isBound is true this will return the current value of this field on the bound ViewModel. Otherwise will always be undefined.

Indicates this field should only be written only and is not intended to be read directly. This is not enforced but can be used by components to adjust their output accordingly (eg. exclude it from a detail view on a record)