# Save mixin
The save
mixin allows to append CRUD
(opens new window) functionality into the model.
# Model definition
The mixin provides a factory function that returns a mixin function. It is a general interface that allows to send arguments to the factory only once.
Of course, it's not necessary to define intermediate classes. Let's optimize the previous example:
# Flags
So, currently our model has a new method save()
and the following readonly flags.
# Creating flag
Type: boolean
. This flag indicating that the mixin is creating a record in the storage.
# Updating flag
Type: boolean
. This flag indicating that the mixin is updating a record in the storage.
# Destroying flag
Type: boolean
. This flag indicating that the mixin is deleting a record from the storage.
# Saving flag
Type: boolean
. This flag indicating that the mixin is saving a record to the storage.
# New flag
Also the save
mixin uses a flag from the base model class. This is a new
flag. The flag indicating that the model data was never saved in the storage.
# Options
The mixin expects an options object with the following interface:
interface SaveOptions<T extends object> extends Options {
/**
* Mixin name.
*/
mixinType: 'save';
/**
* A function that creates a new record in the storage.
*/
create?: CreateFunc<T>;
/**
* A function that saves an existing record to the storage.
*/
update?: UpdateFunc<T>;
/**
* A function that deletes an exisiting record from the storage.
*/
destroy?: DestroyFunc<T>;
}
Type T
is the model data type. That object should be send to the model constructor.
# Creating
You have to define the create
property for SaveOptions
if you want to create new records in your storage. The property must be a function that conforms to this interface:
type CreateFunc<T> = (data: T) => Promise<T | unknown> | T | unknown;
Type T
is the model data type. The data
argument must be an object, that will be send to the storage.
WARNING
If the function returns an object, that object will replace the model data
property. If the function throws an error that error will be thrown outside.
For example we define a create
function (we are using axios
(opens new window)):
Instancing the model and try to save the new record to the storage:
const instance = new Model(undefined, { mixinType: 'save', create });
instance.data.firstName = 'John';
instance.data.lastName = 'Doe';
console.log(instance.new); // outputs: true
async function tryToSave() {
try {
await instance.save();
} catch (error) {
console.error('Could not save an instance by the reason:', error);
console.log(instance.new); // outputs: true
return;
}
console.log('An instance has been saved successfully');
console.log(instance.new); // outputs: false
}
tryToSave(); // call the async function
The creating
flag can be used to enable a loader indicator.
# Updating
Because the mixin doesn't know conditions of the model data identifier, the developer have to reset the new
flag of existing records manually into the model constructor.
Set the update
property for SaveOptions
to enable the record update operation. The property must be a function that conforms to this interface:
type UpdateFunc<T> = (id: unknown, data: T) => Promise<T | unknown> | T | unknown;
The id
argument is a value of a data field which is accessible by idKey
. The data
argument must be an object, that will be send to the storage.
WARNING
If the function returns an object, that object will replace the model data
property. If the function throws an error that error will be thrown outside.
Let's define an update
function:
and test it together with the model:
const instance = new Model({ id: 10, firstName: 'John', lastName: 'Doe' }, { mixinType: 'save', create, update });
console.log(instance.new); // outputs: false
console.log(instance.dirty); // outputs: false
instance.data.firstName = 'James';
instance.data.lastName = 'Smith';
console.log(instance.dirty); // outputs: true
async function tryToSave() {
const isNew = instance.new;
try {
await instance.save();
} catch (error) {
const text = isNew ? 'create' : 'update';
console.error(`Could not ${text} an instance by the reason:`, error);
console.log(instance.dirty); // outputs: true
return;
}
const text = isNew ? 'created' : 'updated';
console.log(`An instance has been ${text} successfully`);
console.log(instance.dirty); // outputs: false
}
tryToSave();
The updating
flag can be used to enable a loader indicator.
# Destroying
Simply define and use the destroy
property of SaveOptions
to enable the record delete operation. The property must be a function that conforms to this interface:
type DestroyFunc<T> = (id: unknown, data: T) => Promise<void> | void;
The id
argument is a value of a data field which is accessible by idKey
. The data
argument must be an object, that may be send to the storage.
WARNING
The record delete operation sets the destroyed
flag, but doesn't destroys the model instance.
It is necessary to define a destroy
function to complete the mixin's options list.
A complete example: