---
product: "AG Studio"
title: "Live Data"
description: "Change rows in the built-in engine with transactions, and keep every widget up to date as the data changes."
framework: vue
version: "3.1.0"
related:
    - title: "Data Overview"
      url: "https://www.ag-grid.com/studio/vue/data/"
    - title: "Data Modelling"
      url: "https://www.ag-grid.com/studio/vue/data-modelling/"
    - title: "Loading Data"
      url: "https://www.ag-grid.com/studio/vue/loading-data/"
    - title: "Sharing & Caching Data"
      url: "https://www.ag-grid.com/studio/vue/sharing-caching-data/"
llms: "https://www.ag-grid.com/studio/llms.txt"
---

# Live Data

Tables in the built-in engine can change in place. The application applies a transaction to add, update or remove rows, and every widget that reads the table shows the change, with no reload.

#### Live Data

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgStudio } from "ag-studio-vue3";
import {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgReportHistory,
  AgReportState,
  AgStudioApi,
  AgStudioApiReadyEvent,
  AgStudioMode,
  AgStudioProperties,
  enableStudioDevValidations,
} from "ag-studio";
import {
  createRandomTransaction,
  getInitialTrades,
  tradeFields,
} from "./data.ts";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

let updateTimer: ReturnType<typeof setInterval> | undefined;

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div style="display: flex; flex-direction: column; height: 100%">
      <div class="example-controls">
        <div class="controls-row">
          <button id="start" v-on:click="startUpdates()">Start Updates</button>
          <button id="stop" v-on:click="stopUpdates()">Stop Updates</button>
        </div>
      </div>
      <ag-studio
        style="width: 100%; height: 100%;"
        class="my-studio-container"
        @api-ready="onApiReady"
        :initialState="initialState"
        :mode="mode"
        :data="data"></ag-studio>
      </div>
        </div>
    `,
  components: {
    "ag-studio": AgStudio,
  },
  setup(props) {
    const studioApi = shallowRef<AgStudioApi | null>(null);
    const initialState = ref<AgReportState | AgReportHistory>({
      pages: [
        {
          id: "page1",
          widgets: {
            table: {
              type: "grid",
              dataMapping: {
                cols: [
                  { id: "trades.desk" },
                  { id: "trades.quantity", aggregation: "sum" },
                ],
              },
              format: { title: { enabled: true, text: "Table" } },
            },
            chart: {
              type: "column-chart-grouped",
              dataMapping: {
                categoryKey: [{ id: "trades.desk" }],
                valueKey: [{ id: "trades.quantity", aggregation: "sum" }],
                tooltipKey: [],
              },
              format: { title: { enabled: true, text: "Quantity by Desk" } },
            },
          },
          widgetLayout: {
            table: { xTrack: 0, yTrack: 0, xSpan: 24, ySpan: 12 },
            chart: { xTrack: 0, yTrack: 12, xSpan: 24, ySpan: 16 },
          },
        },
      ],
      selectedPageId: "page1",
      panels: {
        filters: {
          collapsed: true,
        },
        edit: {
          collapsed: true,
        },
      },
    });
    const mode = ref<AgStudioMode>("edit");
    const data = ref<AgDataSourcesDefinition | AgDataEngine>({
      sources: [
        {
          id: "trades",
          transactions: "all",
          primaryKeyField: "id",
          data: getInitialTrades(),
          fields: tradeFields,
        },
      ],
    });

    function startUpdates() {
      if (updateTimer == null) {
        updateTimer = setInterval(applyRandomChange, 100);
      }
    }
    function stopUpdates() {
      clearInterval(updateTimer);
      updateTimer = undefined;
    }
    const onApiReady = (params: AgStudioApiReadyEvent) => {
      studioApi.value = params.api;
    };
    function applyRandomChange() {
      const engine = studioApi.value.getDataEngine();
      engine?.applyTransaction?.(createRandomTransaction());
    }

    return {
      studioApi,
      initialState,
      mode,
      data,
      onApiReady,
      startUpdates,
      stopUpdates,
      applyRandomChange,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Live Data](https://www.ag-grid.com/studio/examples/live-data/live-data/vue3/)

In the example above, **Start Updates** applies a transaction to the trades table every 100 milliseconds. The table and the chart show each change as it lands.

## Accepting Transactions

A table accepts transactions only when its data source allows them. Set `transactions` on a sync or columnar data source:

- `'none'`, the default, accepts no transactions.
- `'add'` accepts added rows only.
- `'all'` also accepts updated and removed rows. It requires `primaryKeyField`, which names the field that identifies each row.

```ts
<ag-studio
    :data="data"
    /* other studio properties ... */>
</ag-studio>

this.data = {
    sources: [{
        id: 'trades',
        transactions: 'all',
        primaryKeyField: 'id',
        data: [
            { id: 1, desk: 'Rates', quantity: 100 },
            // ... other rows
        ],
    }],
};
```

Tables from an async data source accept no transactions. See [Async Data Sources](https://www.ag-grid.com/studio/vue/live-data/#async-data-sources).

## Applying a Transaction

Get the engine with `api.getDataEngine()` once the `studioReady` event has fired, and call `applyTransaction`.

```ts
const engine = api.getDataEngine();
const position = await engine.applyTransaction({
    tableId: 'trades',
    remove: [2],
    update: [{ id: 1, desk: 'Rates', quantity: 120 }],
    add: [{ id: 3, desk: 'Credit', quantity: 10 }],
});
```

- Rows are identified by the value of their `primaryKeyField`.
- The parts apply in the order `remove`, `update`, `add`. To change a row's key, remove the row and add it again under the new key.
- Each row in `update` replaces the whole row, so give every field the row keeps.
- If any part is invalid, such as a key the table does not hold, the engine rejects the whole transaction and the table does not change.

The promise resolves once the change is committed, so a query made after that sees the new rows. It resolves with the table's new position: a number that goes up with each change to the table.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `tableId` | `string` |  | The table to change. |
| `add` | `AgDataTransactionRows \| AgDataTransactionColumns` |  | Rows to add, as row objects or as columns. Accepted by a table whose `transactions` is `'add'` or `'all'`. On a table with a `primaryKeyField`, each row must carry a key no other row holds once `remove` has applied. |
| `update` | `AgDataTransactionRows \| AgDataTransactionColumns` |  | Replacement rows, as row objects or as columns. Each is a whole row carrying the key of the row it replaces, and every field the row should keep. Requires a table whose `transactions` is `'all'`. |
| `remove` | `unknown[]` |  | Keys of the rows to remove. Requires a table whose `transactions` is `'all'`. |

### Changing Several Tables Together

Pass an array of transactions to change several tables as one change. Either every transaction applies or none does, and no query result reflects some of the tables changed and not others. The promise resolves with each changed table's new position, by table id.

```ts
const positions = await engine.applyTransaction([
    { tableId: 'trades', add: [{ id: 7, desk: 'FX', quantity: 5 }] },
    { tableId: 'desks', add: [{ desk: 'FX', region: 'AMER' }] },
]);
// positions.trades and positions.desks are the two tables' new positions
```

### Frequent Changes

Call `applyTransaction` for each change as it arrives. Transactions apply in the order they are called. Transactions called while an earlier one is still being applied are applied together, and resolve with the same position.

A widget does not run its queries once for each transaction. Changes that land while its queries run are shown together, by the next run.

## What Widgets Show

When a table changes, only the widgets with a query that reads it run their queries again. Filters and cross filters apply to the new data as they did to the old.

- **Tables and Pivot Tables** update in place on the next frame after their queries have run. Selected rows and open row groups stay as they were. They can also animate changed values, using the **Highlight Changes** toggle on the Format tab.
- **Charts** redraw on the next frame after their queries have run.
- **Other widgets** refresh at most once a second, with the latest data. The states between two refreshes are not shown.

When two of a widget's queries read the same table, the widget never shows one query's data from before a change to that table beside the other's from after it. A widget in a hidden browser tab updates when the tab is shown again.

Each widget is kept up to date on its own, so while the data is changing two widgets can briefly show it at different points. Once the data stops changing, every widget shows the latest data within about a second.

A [Custom Widget](https://www.ag-grid.com/studio/vue/custom-widgets/#keeping-data-up-to-date) chooses how it is kept up to date.

## Transactions, Data Updates and Reloads

|  | Transaction | New `data` property | `api.reload()` |
| --- | --- | --- | --- |
| Changes | Rows of the tables named, in place | The data of the sync data sources passed | Nothing in place: async data sources are fetched again |
| Cached results | Kept for the tables not changed | Cleared | Cleared |
| Widgets | Those that read a changed table run their queries again | Every widget loads its data again | Every widget loads its data again |

See [Reloading Sync Data](https://www.ag-grid.com/studio/vue/loading-data/#reloading-data) and [Reloading Async Data](https://www.ag-grid.com/studio/vue/loading-data/#reloading-async-data).

## Async Data Sources

A table from an async data source accepts no transactions. When its data changes, call `api.reload()` to show the change.

> **Warning**
>
> An async data source's `getData` must not run queries on the engine it supplies data to, directly or through Studio, and must not wait for anything that is itself waiting for a transaction. A transaction to a table waits for the queries reading it, and a query can be waiting for `getData`, so such a wait can hold up every update on the dashboard.
