Next Experience: Record Watchers
Tags: servicenow, nextexperience, tutorial, javascript
Published: 2023-01-13 16:50:03

## Precursor

Record watchers have been around for quite some time to provide real-time data on back-end forms in **ServiceNow** as well as in the **Service Portal**. Changes in instance data trigger an event on the front end, upon receiving the event you can then perform logic based on the change to instance data.

Similarly to making **REST API** calls there is a predefined **npm** package you can install to work just like `@servicenow/ui-effect-http`. This package is named `@servicenow/ui-effect-amb` and you will have to install it to utilize record watching functionality.

> Record watchers can not truly be tested until the components are deployed to the instance directly. This is because there is no way to use the proxy from now-cli.json in websockets which is the paradigm that record watchers use.

> This can make debugging quite difficult, so I apologize for the inconvenience.

## Preparation

First things first you need to install the package.

```shell
npm install @servicenow/ui-effect-amb
```

This package needs to be imported to create an `actionHandler` similar to when you import `@servicenow/ui-effect-http`.

```javascript
import { createAmbSubscriptionEffect } from "@servicenow/ui-effect-amb";
```

The function `createAmbSubscriptionEffect` takes two main parameters, a `channelid` string and an `options` object. Below is the definition of the effect and explains which parameters it expects.

`createAmbSubscriptionEffect`

```javascript
 /*
 * @param {String} channelId - AMB Channel ID
 * @param {Object} options
 * @param {String} options.subscribeStartedActionType - Action dispatched when an amb subscription is started
 * @param {String} options.subscribeSucceededActionType - Action dispatched if an amb subscription succeeds
 * @param {String} options.subscribeFailedActionType - Action dispatched if an amb subscription fails
 * @param {String} options.unsubscribeSucceededActionType - Action dispatched when an amb unsubscribe is successful
 * @param {String} options.messageReceivedActionType - Action dispatched when a message is received on a channel
 * @param {Boolean} options.encodeURIComponentForChannelId - Encode channel id URL (default: true)
 *
 * @return {Object} AMB Subscription Effect Descriptor
 */
```

### Channel ID

The **channel ID** is almost always going to be the same string because it essentially serves as the endpoint to kick off the record watcher.

```javascript
//! This string will almost always be:
const channelID = "/rw/default/:table/:filter";
```

## Usage

This function will return an object that can be attached directly to an `actionHandler`. The action handler that it is attached to will serve as the main way to start and stop a record watcher.

To make life easier I created a helper function which generates an `ambSubscriptionEffect` and fills in some default settings. Generally you will only need to really use the `subscribeStartedActionType` and `messageReceivedActionType` properties in the `options` object.

`ambEffectHelper.js`

```javascript
import { createAmbSubscriptionEffect } from "@servicenow/ui-effect-amb";

const channelID = "/rw/default/:table/:filter";

export const createComponentWatcher = (prefix = "DEFAULT_PREFIX", options) => {	
    return createAmbSubscriptionEffect(channelID, {
        subscribeStartedActionType: `${prefix}_WATCHER_DEFAULT_STARTED`,
        subscribeSucceededActionType: `${prefix}_WATCHER_DEFAULT_SUBSCRIBED`,
        subscribeFailedActionType: `${prefix}_WATCHER_DEFAULT_FAILED`,
        unsubscribeSucceededActionType: `${prefix}_WATCHER_DEFAULT_UNSUBSCRIBED`,
        messageReceivedActionType: `${prefix}_WATCHER_DEFAULT_CHANGED`,
        ...options,
    });
};
```

Now that we have our helper function set up we can quickly build `actionHandlers` to start or stop a record watcher subscription.

`actionHandlers.js`

```javascript
import { createComponentWatcher } from "../ambEffectHelper";

export default {
    actionHandlers: {
        
        
        //┌─────────────────────────────────────────────────────────────
        //! This will beging the record watcher subscription
        //! an important detail to note is the subscribe: true
        //! in the dispatch call.
        //└─────────────────────────────────────────────────────────────
        START_RECORD_WATCHER: ({ state, dispatch }) => {
            const recordTable = "incident";
            const recordID = "7f96bd071b32c010f89b99bc1d4bcbf9";
            const originalFilter = `state=1^sys_id=${recordID}`;
            const filter = btoa(originalFilter).replace(/=/g, '-');
            dispatch(`SETUP_RECORD_WATCHER`, {
                table: recordTable,
                filter: filter,
                subscribe: true,
            });
        },
        
        
        //┌─────────────────────────────────────────────────────────────
        //! This actionHandler is nearly the same, but stops the
        //! record watcher instead of starting it.
        //└─────────────────────────────────────────────────────────────
        STOP_RECORD_WATCHER: ({ state, dispatch }) => {
            const recordTable = "incident";
            const recordID = "7f96bd071b32c010f89b99bc1d4bcbf9";
            const originalFilter = `state=1^sys_id=${recordID}`;
            const filter = btoa(originalFilter).replace(/=/g, '-');
            dispatch(`SETUP_RECORD_WATCHER`, {
                table: recordTable,
                filter: filter,
                subscribe: false,
            });
        },
        
        
        //┌─────────────────────────────────────────────────────────────
        //! This actionHandler allows you to start and stop
        //! a record watcher using our helper function.
        //└─────────────────────────────────────────────────────────────
        SETUP_RECORD_WATCHER: createComponentWatcher("RECORD", {
            subscribeStartedActionType: `RECORD_WATCHER_STARTED`,
            messageReceivedActionType: `RECORD_WATCHER_ITEM_CHANGED`,
        }),
        
        
        //┌─────────────────────────────────────────────────────────────
        //! This is really just to let you know a subscription
        //! to a record watcher has been started. Serves no other
        //! purpose.
        //└─────────────────────────────────────────────────────────────
        RECORD_WATCHER_STARTED: (coeffects) => {
            console.log(`RECORD_WATCHER_STARTED`, coeffects);
        },
        
        
        //┌─────────────────────────────────────────────────────────────
        //! This is the most important actionHandler, it will be
        //! triggered any time a record that fits the query is
        //! inserted/updated/deleted.
        //!
        //! From here you can perform any logic you want.
        //└─────────────────────────────────────────────────────────────
        RECORD_WATCHER_ITEM_CHANGED: ({ action, dispatch }) => {
            const { payload } = action;
            console.log(`RECORD_WATCHER_ITEM_CHANGED`, payload);
            const { data: { operation } } = payload;
            
            //! Do something based on the operation
            //! insert | update | delete
            dispatch(`RECORD_WATCHER_${operation}`, payload);
        },
        
        
    }
}
```

## Conclusion

With this basic setup you can now perform any logic you want when a record is inserted, updated or deleted within the `encodedQuery` you define for your record watcher.

Since components can't always be directly attached to each other, this is a nice way to form a basic type of communication between different components.

### Disclaimer

Again, the only way to truly test to see if your record watcher is working is to deploy your component to your instance, this a major detractor in the setup process, but with some effort you too can begin record watching!