Create an observable data action
This article describes how to create an observable data action in Microsoft Dynamics 365 Commerce.
Observable data actions are used to track the status of a data action as it's running. This capability is helpful if you must run logic or render a user interface (UI) in response to the current status of a data action. Observable data actions use a special promise-like class that is named AsyncResult. This class adds "observer" functionality to a standard promise.
The following example shows how to take advantage of an AsyncResult class. This data action waits three seconds before it returns a string.
// test-action.ts
/**
* Test action method for TestAsync action
* @param input The action input
* @param context The action context
*/
const testAction = async (input: TestAsyncActionInput, context: IActionContext): Promise<string> => {
await new Promise(resolve => {
setTimeout(
() => {
resolve();
}, 3000);
});
// Flag-based error scenario
if (input.shouldError) {
throw new Error('Oops');
}
return 'foo';
};
/**
* Test Action Input
*/
export class TestAsyncActionInput implements IActionInput {
public shouldError: Boolean = false;
constructor(shouldError?: Boolean) {
this.shouldError = shouldError || this.shouldError;
}
public getCacheKey = () => `test`;
public getCacheObjectType = () => 'test';
public dataCacheType = (): Msdyn365.CacheType => 'none';
}
/**
* Test createInput method for TestAsync Action
*/
const createInput = () => {
return new TestAsyncActionInput();
};
In this example, the data action simulates an outgoing application programming interface (API) call. It also includes a flag that can be set to allow a failure simulation. In some scenarios, you might want to create a module that can send updates about the status of a data action. You can't use a regular data action for that purpose.
Create the observable data action
To create the observable data action, use the new createObservableDataAction utility method for data action creation.
export default createObservableDataAction({
input: createInput,
action: <IAction<string>>testAction
});
The createObservableDataAction method returns an IObservableAction interface. The IObservableAction interface returns an AsyncResult class that provides additional data including the status and error properties of the data action.
A mock can be created to test the data action, as shown in the following example.
// test-module.tsx
import { observer } from 'mobx-react';
import * as React from 'react';
import { IAsyncTestModuleData } from './async-test-module.data';
import { IAsyncTestModuleProps } from './async-test-module.props.autogenerated';
import testAction, { TestAsyncActionInput } from './actions/async-test-action';
/**
* TestModule Component
* @extends {React.PureComponent<IAsyncTestModuleProps<IAsyncTestModuleData>>}
*/
@observer
class AsyncTestModule extends React.PureComponent<IAsyncTestModuleProps<IAsyncTestModuleData>> {
constructor(props: IAsyncTestModuleProps<IAsyncTestModuleData>) {
super(props);
}
public render(): JSX.Element {
return (
<div className='row'>
<div className='col'>
<h1>Status: { this.props.data.testResult.status }</h1>
<h1>Result: { this.props.data.testResult.result }</h1>
{ this.props.data.testResult.error &&
<h1>Error: { this.props.data.testResult.error.message }</h1>
}
<button
// tslint:disable:jsx-no-lambda
// tslint:disable-next-line:react-this-binding-issue
onClick={(e) => this._clientCall(e)}
> Run on client
</button>
<button
// tslint:disable:jsx-no-lambda
// tslint:disable-next-line:react-this-binding-issue
onClick={(e) => this._clientCall(e, true)}
> Error on client
</button>
</div>
</div>
);
}
}
export default AsyncTestModule;
The following example shows a sample module definition that registers the sample observable data action that was created earlier.
// test-module.definition.json
{
"$type": "contentModule",
"friendlyName": "test-module",
"name": "test-module",
"description": "Module for testing observable data actions",
"categories": ["test-module"],
"tags": ["samples"],
"dataActions": {
"testResult":{
"path": "./actions/test-action",
"runOn": "client"
}
}
}
When adding a data action inside the module data.ts file, ensure that every data action that returns an observableDataAction object is wrapped by an AsyncResult class. This will guarantee the correct typings when a module is written.
// test-module.data.ts
import { AsyncResult } from '@msdyn365-commerce/retail-proxy';
export interface IAsyncTestModuleData {
testResult: AsyncResult<string>;
}
When a data action is wrapped in an AsyncResult class (as in the above example), the module will now have access to the status, result, and error properties. The status property contains the current state of the data action, which can be one of: 'Success', 'Loading' or 'Failed'. The result property contains the data that is returned by the action if it succeeds. If the data action throws an error, the result property won't be filled in. Instead, the error property can be used to see the error details.
By taking advantage of the status, result, and error properties that are provided by observable data actions, complicated scenarios can be better handled in a module. Examples include showing a loading screen while a data action call runs and providing contextual error messages in response to a failed data action.