ActMasterAction
Action is the place to store your business logic.
This is essentially an implementation of the Commander pattern.
Action is an object with the required name property and the exec method.
Simple action
// simplest-action.ts
import { ActMasterAction } from 'vue-act-master';
export const dataAction: ActMasterAction = {
name: 'get.data',
async exec(id = 1) {
const url = `https://jsonplaceholder.typicode.com/todos/${ id }`;
const response = await fetch(url);
return response.json();
// => {
// userId: 1,
// id: 1,
// title: "delectus aut autem",
// completed: false
// }
},
};Having added this action, we can now call it and get the result.
Now we can call exec using the action name.
this.value = await this.$act.exec('get.data');You can also pass arguments to the exec method.
this.value = await this.$act.exec('get.data', 101);Class Styled Action
You can use classes to create actions.
Along with classes, it will be possible to use decorators helper.
We recommend using classes. They are more desirable, especially if you are using TypeScript.
// class-action.ts
import { ActMasterAction } from 'vue-act-master';
export class ClassAction implements ActMasterAction {
name = 'get.data';
async exec() {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const response = await fetch(url);
return response.json();
}
}// ../act/actions
export const actions: ActMasterAction[] = [
new ClassAction(),
]Cancel Action
Action can be interrupted by returning a special object "CancelledAct". This will stop the chain of events if you build it using watch or emit.
// cancel-action.ts
import { ActMasterAction, CancelledAct } from 'vue-act-master';
export class DataAction implements ActMasterAction {
name = 'get.data',
exec() {
// ...
return new CancelledAct('Some reason to stop action...');
},
};Validate arguments
Before calling the exec method, you can validate the arguments that are sent to it.
We add a method validateInput to which all arguments intended for exec get.
If they are valid we return true.
Otherwise an error message of your choice.
// validate-action.ts
import { ActMasterAction, CancelledAct } from 'vue-act-master';
export class DataAction implements ActMasterAction {
name = 'get.data',
validateInput(arg?: any): true | CancelledAct {
if (typeof arg !== 'number') {
const errorData = {
id: 'Must be a number'
};
return new CancelledAct('Validation error', errorData);
}
return true; // If everything is correct
}
async exec(id: number) {
const url = `https://jsonplaceholder.typicode.com/todos/${id}`;
const response = await fetch(url);
return response.json();
},
};And try to exec
const result = await this.$act.exec('get.data', '101');
if (result instaceof CancelledAct) {
// ... Handling the error
return;
}With transformation
It often happens that the result of a call needs to be changed.
This can easily be done through the transform method.
The argument will be the result of the exec method.
// transformed-action.ts
import { ActMasterAction } from 'vue-act-master';
export class TransformedAction implements ActMasterAction {
name = 'get.data.transformed',
async exec() {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const response = await fetch(url);
return response.json();
// Output
// {
// userId: 1,
// id: 1,
// title: "delectus aut autem",
// completed: false
// }
}
transform(data) {
// Modifies the data after receiving
return {
todoItem: data.title,
done: data.completed,
};
}
};And try to exec
const result = await this.$act.exec('get.data.transformed');
console.log(result); // => { todoItem: "delectus aut autem", done: false }Watch
You can launch the action after another one through the "watch" property.
Any of the actions in watch, after execution, will call the current action.
WARNING
Be careful. The action should not follow itself. Otherwise it will start an endless loop.
// Action queue
import { ActMasterAction } from 'vue-act-master';
export class FirstAction implements ActMasterAction {
name = 'FirstAction';
exec() {
return {
name: 'Leo',
};
}
};
export class SecondAction implements ActMasterAction {
// Names of events, after any and which action automatically starts.
watch: ['FirstAction'],
name = 'SecondAction';
exec(data) {
console.log(data); // { "Name": "Leo" }
return {
name: 'Mike',
};
}
};inProgress
If you need to show the progress bar on the desired command, you can use the inProgress method.
It takes a function which will be called when the state of the action changes.
// App.vue
<script>
export default {
data() {
return {
// status of process
isLoading: false,
// There will be an unsubscribe function to avoid memory leaks
off: () => null,
};
},
mounted() {
this.off = this.inProgress('GetData', (status: boolean) => {
this.isLoading = status;
});
// Call action
this.$act.exec('GetData');
},
beforeDestroy() {
this.off(); // unsubscribe
}
}
</script>If you use classes and decorators, you can write it down shorter.
// App.vue - with decorators
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { ActInProgress } from 'vue-act-master';
@Component
export default class MyVueComponent extends Vue {
@ActInProgress('GetData')
isLoading = false;
mounted() {
this.$act.exec('GetData');
}
}
</script>DI in Actions
To make the actions more independent, a simple Dependency injection (DI) implementation has been added.
It consists of storing entities that we access from actions into an internal container.
For example, we have an API. And no matter what we use (REST,GraphQL,gRPC), this interface will not change.
And we just use the implementation of this entity through an interface.
In the code it looks like this:
Adding an entity
// main.ts
// ...
import { VueActMaster } from 'vue-act-master';
import { actions } from '../act/actions';
import { SuperAPI } from '../you/api';
const di = {
// key, value
'api': SuperAPI
};
Vue.use(VueActMaster, {
actions,
di, // map of entities
});or in vue component
// App.vue
<script>
import { SuperAPI } from '../you/api';
export default {
data() {
return {};
},
mounted() {
// Adding DI scope
this.$act.setDI('api', SuperAPI);
}
}
</script>Using entities
There are two ways to get DI entities:
- Through decorators, if you use them in typescript.
- Through the
useDImethod.
// with-di-action.ts
// with decorator
import { UseDI, ActMasterAction } from 'vue-act-master';
import { SuperAPI } from '../you/api';
export class WithDiAction implements ActMasterAction {
name = 'login';
@UseDI('api')
private api!: SuperAPI; // SuperAPI as interface
exec(loginData) {
return this.api.login(loginData);
}
}OR
// with-di-action.ts
// without decorator
import { ActMasterAction } from 'vue-act-master';
import { SuperAPI } from '../you/api';
export class WithDiAction implements ActMasterAction {
name = 'login';
private api: SuperAPI; // SuperAPI as interface
exec(loginData) {
return this.api.login(loginData);
}
// get DI scope
useDI({ api }) {
this.api = api;
}
}Emit another Action in Action
If you need to call another action inside the current one, you can do it using the emit handler.
It can be connected via the decorator or via the helper.
This way you can build chains of actions that can be stopped by CancelledAct.
You can use this to return different values to subscribers.
// with-emit-action.ts
import { ActMasterAction, EmitAction } from 'vue-act-master';
export class WithEmitAction implements ActMasterAction {
name = 'login';
private emit: EmitAction;
exec(loginData) {
const result = api.login(loginData);
// use another action
this.emit('set.authorized', true);
}
// set Emitter
useEmit(emit: EmitAction) {
this.emit = emit;
}
}OR
// with-emit-action.ts
// with decorator
import { ActMasterAction, Emit, EmitAction } from 'vue-act-master';
export class WithEmitAction implements ActMasterAction {
name = 'login';
@Emit()
private emit!: EmitAction;
exec(loginData) {
const result = api.login(loginData);
// use another action
this.emit('set.authorized', true)
}
};