Vue ViewModel Layer
The ViewModel layer, generated as viewmodels.g.ts
, exports a ViewModel class for each API-backed type in your data model (Entity Models, Custom DTOs, and Services). It also exports a ListViewModel type for Entity Models and Custom DTOs.
These classes provide a wide array of functionality that is useful when interacting with your data model through a user interface. The generated ViewModels are the primary way that Coalesce is used when developing a Vue application.
ViewModels
The following members can be found on the generated Entity and Custom DTO ViewModels, exported from viewmodels.g.ts
as <TypeName>ViewModel
.
Model Data Properties
Each ViewModel class implements the corresponding interface from the Model Layer, meaning that the ViewModel has a data property for each Property on the model. Object-typed properties will be typed as the corresponding generated ViewModel.
Changing the value of a property will automatically flag that property as dirty. See Auto-save & Dirty Flags below for information on how property dirty flags are used.
There are a few special behaviors when assigning to different kinds of data properties on View Models as well:
Model Object Properties
- If the object being assigned to the property is not a ViewModel instance, a new instance will be created automatically and used instead of the incoming object.
- If the model property is a reference navigation, the corresponding foreign key property will automatically be set to the primary key of that object. If the incoming value was null, the foreign key will be set to null.
- If deep auto-saves are enabled on the instance being assigned to, auto-save will be spread to the incoming object, and to all other objects reachable from that object.
Model Collection Properties
- When assigning an entire array, any items in the array that are not a ViewModel instance will have an instance created for them.
- The same rule goes for pushing items into the existing array for a model collection - a new ViewModel instance will be created and be used instead of the object(s) being pushed.
Foreign Key Properties
If the corresponding navigation property contains an object, and that object's primary key doesn't match the new foreign key value being assigned, the navigation property will be set to null.
Other Data Properties & Functions
readonly $metadata: ModelType
readonly $metadata: ModelType
The metadata object from the Metadata Layer layer for the type represented by the ViewModel.
readonly $stableId: number
readonly $stableId: number
An immutable number that is unique among all ViewModel instances, regardless of type.
Useful for uniquely identifying instances with :key="vm.$stableId"
in a Vue component, especially for instances that lack a primary key.
$primaryKey: string | number
$primaryKey: string | number
A getter/setter property that wraps the primary key of the model. Used to interact with the primary key of any ViewModel in a polymorphic way.
$display(prop?: string | Property): string
$display(prop?: string | Property): string
Returns a string representation of the object, or one of its properties if specified, suitable for display.
This is especially useful for displaying enum properties, navigation properties, date properties, and other complex values.
$addChild(prop: string | ModelCollectionNavigationProperty, initialDirtyData?: {})
$addChild(prop: string | ModelCollectionNavigationProperty, initialDirtyData?: {})
Creates a new instance of an item for the specified child model collection, adds it to that collection, and returns the item. If initialDirtyData
is provided, it will be loaded into the new instance with $loadDirtyData()
.
Loading & Parameters
$load: ItemApiState;
$load(id?: TKey) => ItemResultPromise<TModel>;
$load: ItemApiState;
$load(id?: TKey) => ItemResultPromise<TModel>;
An API Caller for the /get
endpoint. Accepts an optional id
argument - if not provided, the ViewModel's $primaryKey
is used instead. Uses the instance's $params
object for the Standard Parameters.
$params: DataSourceParameters
$params: DataSourceParameters
An object containing the Standard Parameters to be used for the $load
, $save
, $bulkSave
, and $delete
API callers.
$dataSource: DataSource
$dataSource: DataSource
Getter/setter wrapper around $params.dataSource
. Takes an instance of a Data Source class generated in the Model Layer.
$includes: string | null
$includes: string | null
Getter/setter wrapper around $params.includes
. See Includes String for more information.
$loadCleanData(source: {} | TModel, purgeUnsaved = false)
$loadCleanData(source: {} | TModel, purgeUnsaved = false)
Loads data from the provided model into the current ViewModel, and then clears all dirty flags.
Data is loaded recursively into all related ViewModel instances, preserving existing instances whose primary keys match the incoming data.
If auto-save is enabled, only non-dirty properties are updated. This prevents user input that is pending a save from being overwritten by the response from an auto-save /save
request.
If purgeUnsaved
is true, items without a primary key will be dropped from collection navigation properties. This is used by the $load
caller in order to fully reset the object graph with the state from the server.
$loadDirtyData(source: {} | TModel)
$loadDirtyData(source: {} | TModel)
Same as $loadCleanData
, but does not clear any existing dirty flags, nor does it clear any dirty flags that will be set while mutating the data properties of any ViewModel instance that gets loaded.
constructor(initialDirtyData?: {} | TModel | null)
constructor(initialDirtyData?: {} | TModel | null)
Create a new instance of the ViewModel, loading the given initial data with $loadDirtyData()
if provided.
Saving and Deleting
$save: ItemApiState;
$save(overrideProps?: Partial<TModel>) => ItemResultPromise<TModel>;
$save: ItemApiState;
$save(overrideProps?: Partial<TModel>) => ItemResultPromise<TModel>;
An API Caller for the /save
endpoint. Uses the instance's $params
object for the Standard Parameters. A save operation saves only properties on the model it is called on - for deep/bulk saves, see $bulkSave.
This caller is used for both manually-triggered saves in custom code and for auto-saves. If the Rules/Validation report any errors when the caller is invoked, an error will be thrown.
overrideProps
can provide properties to save that override the data properties on the ViewModel instance. This allows for manually saving a change to a property without setting the property on the ViewModel instance into a dirty state. This makes it easier to handle some scenarios where changing the value of the property may put the UI into a logically inconsistent state until the save response has been returned from the server - for example, if a change to one property affects the computed value of other properties.
When a save creates a new record and a new primary key is returned from the server, any entities attached to the current ViewModel via a collection navigation property will have their foreign keys set to the new primary key. This behavior, combined with the usage of deep auto-saves, allows for complex object graphs to be constructed even before any model in the graph has been created.
When a save is in progress, the names of properties being saved are in contained in $savingProps
.
Saving behavior can be further customized with $loadResponseFromSaves
and $saveMode
, listed below.
$delete: ItemApiState;
$delete() => ItemResultPromise<TModel>;
$delete: ItemApiState;
$delete() => ItemResultPromise<TModel>;
An API Caller for the /delete
endpoint. Uses the instance's $params
object for the Standard Parameters.
If the object was loaded as a child of a collection, it will be removed from that collection upon being deleted. Note that ViewModels currently only support tracking of a single parent collection, so if an object is programmatically added to additional collections, it will only be removed from one of them upon delete.
$loadResponseFromSaves: boolean
$loadResponseFromSaves: boolean
Default true
- controls if a ViewModel will be loaded with the data from the model returned by the /save
endpoint when saved with the $save
API caller. There is seldom any reason to disable this.
$savingProps: ReadonlySet<string>
$savingProps: ReadonlySet<string>
When $save.isLoading == true
, contains the properties of the model currently being saved by $save
(including auto-saves). Does not include non-dirty properties even if $saveMode == 'whole'
.
This can be used to make per-property UI state changes during saves - for example, displaying progress indicators on/near individual inputs, or disabling input controls.
$saveMode: 'surgical' | 'whole'
$saveMode: 'surgical' | 'whole'
Configures which properties of the model are sent to the server during a save or bulk save.
"surgical"
(default)
By default, only dirty properties (and always the primary key) are sent to the server when performing a save.
This improves the handling of concurrent changes being made by multiple users against different fields of the same entity at the same time - specifically, it prevents a user with a stale value of some field X from overwriting a more recent value of X in the database when the user is only making changes to some other property Y and has no intention of changing X.
Save mode "surgical"
doesn't help when multiple users are editing field X at the same time - if such a scenario is applicable to your application, you must implement more advanced handling of concurrency conflicts.
WARNING
Surgical saves require DTOs on the server that are capable of determining which of their properties have been set by the model binder, as surgical saves are sent from the client by entirely omitting properties from the x-www-form-urlencoded
body that is sent to the server.
The Generated C# DTOs implement the necessary logic for this; however, any Custom DTOs must have this logic manually written by you, the developer. Either implement the same pattern that can be seen in the Generated C# DTOs, or do not use surgical saves with Custom DTOs.
"whole"
All serializable properties of the object are sent back to the server with every save.
$getPropDirty(propName: string): boolean
$getPropDirty(propName: string): boolean
Returns true if the given property is flagged as dirty.
$setPropDirty(propName: string, dirty: boolean = true, triggerAutoSave = true)
$setPropDirty(propName: string, dirty: boolean = true, triggerAutoSave = true)
Manually set the dirty flag of the given property to the desired state. This seldom needs to be done explicitly, as mutating a property will automatically flag it as dirty.
If dirty
is true and triggerAutoSave
is false, auto-save (if enabled) will not be immediately triggered for this specific flag change. Note that a future change to any other property's dirty flag will still trigger a save of all dirty properties.
$isDirty: boolean
$isDirty: boolean
Getter/setter that summarizes the model's property-level dirty flags. Returns true if any properties are dirty.
When set to false, all property dirty flags are cleared. When set to true, all properties are marked as dirty.
Auto-save
// Vue Options API
$startAutoSave(vue: Vue, options: AutoSaveOptions<this> = {})
// Vue Composition API
$useAutoSave(options: AutoSaveOptions<this> = {})
// Vue Options API
$startAutoSave(vue: Vue, options: AutoSaveOptions<this> = {})
// Vue Composition API
$useAutoSave(options: AutoSaveOptions<this> = {})
Starts auto-saving of the instance when its savable data properties become dirty. Saves are performed with the $save
API Caller (documented above) and will not be performed if the ViewModel has any validation errors - see Rules/Validation below.
type AutoSaveOptions<TThis> =
{
/** Time, in milliseconds, to debounce saves for. */
wait?: number;
/** If true, auto-saving will also be enabled for all view models that are
reachable from the navigation properties & collections of the current view model. */
deep?: boolean;
/** Additional options to pass to the third parameter of lodash's `debounce` function. */
debounce?: DebounceSettings;
/** A function that will be called before autosaving that can return false to prevent a save.
Only allowed if not using deep auto-saves.
*/
predicate?: (viewModel: TThis) => boolean;
}
$stopAutoSave(): void
$stopAutoSave(): void
Turns off auto-saving of the instance. Does not recursively disable auto-saves on related instances if deep
was used when auto-save was enabled.
readonly $isAutoSaveEnabled: boolean
readonly $isAutoSaveEnabled: boolean
Returns true if auto-save is currently active on the instance.
Bulk saves
$bulkSave: ItemApiState;
$bulkSave(options: BulkSaveOptions) => ItemResultPromise<TModel>;
$bulkSave: ItemApiState;
$bulkSave(options: BulkSaveOptions) => ItemResultPromise<TModel>;
Bulk saves save all changes to an object graph in one API call and one database transaction. This includes creation, updates, and deletions of entities.
To use bulk saves, you can work with your ViewModel instances on the client much in the same way you would on the server with Entity Framework. Assign objects to reference navigation properties and modify scalar values to perform creates and updates. To perform deletions, you must call model.$remove()
on the ViewModel you want to remove, similar how you would call DbSet<>.Remove(model)
on the server.
If the client-side Rules/Validation report any errors for any of the models being saved in the operation, an error will be thrown.
On the server, each affected entity is handled through the same standard mechanisms as are used by individual saves or deletes (Behaviors, Data Sources, and Security Attributes), but with a bit of sugar on top:
- All operations are wrapped in a single database transaction that is rolled back if any individual operation fails.
- Foreign keys will be fixed up as new items are created, allowing a parent and child record to be created at the same time even when the client has no foreign key to link the two together.
For the response to a bulk save, the server will load and return the root ViewModel that $bulkSave
was called upon, using the instance's $params
object for the Standard Parameters.
export interface BulkSaveOptions {
/** A predicate that will be applied to each modified model
* to determine if it should be included in the bulk save operation.
*
* The predicate is applied before validation (`$hasError`), allowing
* it to be used to skip over entities that have client validation errors
* that would otherwise cause the entire bulk save operation to fail.
* */
predicate?: (viewModel: ViewModel, action: "save" | "delete") => boolean;
/** Additional root items that will be traversed for items that need saving.
* Use to add items that aren't attached to the target of the bulk save,
* but are still desired to be saved during the same operation.
*/
additionalRoots?: ViewModel[];
}
$bulkSavePreview(options?: BulkSaveOptions) => {
isDirty: boolean;
errors: string[];
items: BulkSaveRequestItem[];
rawItems: BulkSaveRequestRawItem[];
}
$bulkSavePreview(options?: BulkSaveOptions) => {
isDirty: boolean;
errors: string[];
items: BulkSaveRequestItem[];
rawItems: BulkSaveRequestRawItem[];
}
Returns the payload that will be used for the $bulkSave
operation.
Useful for driving UI state, like preemptively showing errors, or determining if there are any objects with pending modifications. If you are using this to drive UI state, it is strongly recommended to wrap this call in a computed
in your component to reduce excessive invocations.
$remove(): void
$remove(): void
Removes the item from its parent collection (if it is in a collection), and marks the item for deletion in the next bulk save.
readonly $isRemoved: boolean
readonly $isRemoved: boolean
Returns true if the instance was previously removed by calling $remove()
.
Rules/Validation
$addRule(prop: string | Property, identifier: string, rule: (val: any) => true | string)
$addRule(prop: string | Property, identifier: string, rule: (val: any) => true | string)
Add a custom validation rule to the ViewModel for the specified property. identifier
should be a short, unique slug that describes the rule; it is not displayed in the UI, but is used if you wish to later remove the rule with $removeRule()
.
The function you provide should take a single argument that contains the current value of the property, and should either return true
to indicate that the validation rule has succeeded, or a string that will be displayed as an error message to the user.
Any failing validation rules on a ViewModel will prevent that ViewModel's $save
caller from being invoked.
$removeRule(prop: string | Property, identifier: string)
$removeRule(prop: string | Property, identifier: string)
Remove a validation rule from the ViewModel for the specified property and rule identifier.
This can be used to remove either a rule that was provided by the generated Metadata Layer, or a custom rule that was added by $addRule
. Reference your generated metadata file metadata.g.ts
to see any generated rules and the identifiers they use.
$getRules(prop: string | Property): ((val: any) => string | true)[]
$getRules(prop: string | Property): ((val: any) => string | true)[]
Returns an array of active rule functions for the specified property, or undefined
if the property has no active validation rules.
$getErrors(prop?: string | Property): Generator<string>
$getErrors(prop?: string | Property): Generator<string>
Returns a generator that provides all error messages for either a specific property (if provided) or the entire model (if no prop argument is provided).
TIP
You can obtain an array from a generator with Array.from(vm.$getErrors())
or [...vm.$getErrors()]
readonly $hasError: boolean
readonly $hasError: boolean
Indicates if any properties have validation errors.
Generated Members
API Callers
For each of the instance Methods of the type, an API Caller will be generated.
addTo*()
Functions
For each collection navigation property, a method is generated that will create a new instance of the ViewModel for the collected type, add it to the collection, and then return the new object.
Many-to-many helper collections
For each collection navigation property annotated with [ManyToMany], a getter-only property is generated that returns a collection of the object on the far side of the many-to-many relationship. Nulls are filtered from this collection.
ListViewModels
The following members can be found on the generated ListViewModels, exported from viewmodels.g.ts
as *TypeName*ListViewModel
.
Data Properties
readonly $items: T[]
readonly $items: T[]
Collection holding the ViewModel instances from the last successful invocation of the $load
API Caller.
readonly $modelItems: T[]
readonly $modelItems: T[]
Collection holding plain Model instances from the last successful invocation of the $load
API Caller.
$modelOnlyMode: boolean
$modelOnlyMode: boolean
When model-only mode is enabled, $items
will not be populated with ViewModel instances. Result can instead be read from $modelItems
. This mode allows much better performance when loading large quantities of data, especially in read-only contexts where the features of ViewModel instances aren't needed.
Parameters & API Callers
$params: ListParameters
$params: ListParameters
An object containing the Standard Parameters to be used for the $load
and $count
API callers.
$dataSource: DataSource
$dataSource: DataSource
Getter/setter wrapper around $params.dataSource
. Takes an instance of a Data Source class generated in the Model Layer.
$includes: string | null
$includes: string | null
Getter/setter wrapper around $params.includes
. See Includes String for more information.
$load: ListApiState;
$load() => ListResultPromise<TModel>
$load: ListApiState;
$load() => ListResultPromise<TModel>
An API Caller for the /list
endpoint. Uses the instance's $params
object for the Standard Parameters.
Results are available in the $items
property. The result
property of the $load
API Caller contains the raw results and is not recommended for use in general development - $items
should always be preferred.
$count: ItemApiState;
$count() => ItemResultPromise<number>
$count: ItemApiState;
$count() => ItemResultPromise<number>
An API Caller for the /count
endpoint. Uses the instance's $params
object for the Standard Parameters.
The result is available in $count.result
- this API Caller does not interact with other properties on the ListViewModel like $pageSize
or $pageCount
.
readonly $hasPreviousPage: boolean
readonly $hasNextPage: boolean
readonly $hasPreviousPage: boolean
readonly $hasNextPage: boolean
Properties which indicate if $page
can be decremented or incremented, respectively. $pageCount
and $page
are used to make this determination.
$previousPage(): void
$nextPage(): void
$previousPage(): void
$nextPage(): void
Methods that will decrement or increment $page
, respectively. Each does nothing if there is no previous or next page as returned by $hasPreviousPage
and $hasNextPage
.
$page: number
$page: number
Getter/setter wrapper for $params.page
. Controls the page that will be requested on the next invocation of $load
.
$pageSize: number
$pageSize: number
Getter/setter wrapper for $params.pageSize
. Controls the page that will be requested on the next invocation of $load
.
readonly $pageCount: number
readonly $pageCount: number
Shorthand for $load.pageCount
- returns the page count reported by the last successful invocation of $load
.
Auto-Load
// Vue Options API
$startAutoLoad(vue: Vue, options: AutoLoadOptions<this> = {})
// Vue Composition API
$useAutoLoad(options: AutoLoadOptions<this> = {})
// Vue Options API
$startAutoLoad(vue: Vue, options: AutoLoadOptions<this> = {})
// Vue Composition API
$useAutoLoad(options: AutoLoadOptions<this> = {})
Starts auto-loading of the list as changes to its parameters occur. Loads are performed with the $load
API Caller.
type AutoLoadOptions<TThis> =
{
/** Time, in milliseconds, to debounce loads for. */
wait?: number;
/** Additional options to pass to the third parameter of lodash's `debounce` function. */
debounce?: DebounceSettings;
/** A function that will be called before loading that can return false to prevent a load. */
predicate?: (viewModel: TThis) => boolean;
/** If true, an immediate initial load of the list will be performed.
* Otherwise, the initial auto-load of the list won't occur until
* the first change to its parameters occur. */
immediate?: boolean;
}
$stopAutoLoad()
$stopAutoLoad()
Manually turns off auto-loading of the instance.
Auto-save
// Vue Options API
$startAutoSave(vue: Vue, options: AutoSaveOptions<this> = {})
// Vue Composition API
$useAutoSave(options: AutoSaveOptions<this> = {})
// Vue Options API
$startAutoSave(vue: Vue, options: AutoSaveOptions<this> = {})
// Vue Composition API
$useAutoSave(options: AutoSaveOptions<this> = {})
Enables auto-save for the items in the list, propagating to new items as they're added or loaded. See ViewModel auto-save documentation for more details.
$stopAutoSave(): void
$stopAutoSave(): void
Turns off auto-saving of the items in the list, and turns of propagation of auto-save to any future items if auto-save was previously turned on for the list. Only affects items that are currently in the list's $items
.
readonly $isAutoSaveEnabled: boolean
readonly $isAutoSaveEnabled: boolean
Returns true if auto-save is currently active on the instance.
Generated Members
API Callers
For each of the static Methods on the type, an API Caller will be created.
Service ViewModels
The following members can be found on the generated Service ViewModels, exported from viewmodels.g.ts
as <ServiceName>ViewModel
.
Generated Members
API Callers
For each method of the Service, an API Caller will be created.