> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ably/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# LiveCounter

`LiveCounter` is a synchronized numerical counter that supports increment and decrement operations. It ensures that all updates are correctly applied and synchronized across clients in realtime, preventing inconsistencies when multiple clients modify the counter value simultaneously.

You interact with `LiveCounter` through a PathObject or by obtaining a specific Instance.

## Create a counter

Create a `LiveCounter` using the `LiveCounter.create()` static method and assign it to a path:

```javascript theme={null}
import { LiveCounter } from 'ably/liveobjects';

// Create a counter with initial value 0
await myObject.set('visits', LiveCounter.create(0));

// Create a counter with initial value 100
await myObject.set('score', LiveCounter.create(100));

// Create a counter without specifying initial value (defaults to 0)
await myObject.set('clicks', LiveCounter.create());
```

`LiveCounter.create()` returns a value type that describes the initial value for a new counter. The actual object is created when assigned to a path. Each assignment creates a distinct object with its own unique ID:

```javascript theme={null}
const counterValue = LiveCounter.create(0);

// Each assignment creates a different object
await myObject.set('counter1', counterValue);
await myObject.set('counter2', counterValue);

// counter1 and counter2 are different objects with different IDs
const id1 = myObject.get('counter1').instance()?.id();
const id2 = myObject.get('counter2').instance()?.id();
console.log(id1 === id2); // false
```

## Get counter value

Access a `LiveCounter` through a PathObject for path-based operations, or obtain a specific Instance to work with the underlying object directly. Use the `value()` method to get the value of the `LiveCounter`:

```javascript theme={null}
const myObject = await channel.object.get();

// PathObject access: path-based operations that resolve at runtime
const visits = myObject.get('visits');
console.log(visits.value()); // e.g. 5

// Instance access: reference to a specific counter object
const visitsInstance = myObject.get('visits').instance();
console.log(visitsInstance?.value()); // e.g. 5
```

<Aside data-type="note">
  Calling `value()` on a `PathObject` may return `undefined` if a primitive value or `LiveCounter` instance cannot be found at that path. Calling `value()` on an `Instance` returns `undefined` if the instance is not a primitive or `LiveCounter`.
</Aside>

## Get compact object

Get a numeric representation of the counter using the `compact()` or `compactJson()` methods. These methods return the same result as `value()`:

```javascript theme={null}
// Get a PathObject to a LiveCounter stored in 'visits'
const visits = myObject.get('visits');
console.log(visits.compact());  // e.g. 5

// Get the Instance of a LiveCounter stored in 'visits'
const visitsInstance = myObject.get('visits').instance();
console.log(visitsInstance?.compact());  // e.g. 5
```

When calling these methods on a `LiveMap`, nested `LiveCounter` objects are included as a `number`:

```javascript theme={null}
// Get a PathObject to a LiveMap stored in 'stats'
const stats = myObject.get('stats');
console.log(stats.compact());  // e.g. { visits: 5 }

// Get the Instance of a LiveMap stored in 'stats'
const visitsInstance = myObject.get('stats').instance();
console.log(visitsInstance?.compact());  // e.g. 5
```

## Increment a counter

Increment the value of a `LiveCounter` using the `increment()` method. You can specify an amount to increment by, or use the default increment of 1:

```javascript theme={null}
// PathObject: increment counter at path
await myObject.get('visits').increment(5);  // increment by 5
await myObject.get('visits').increment();   // increment by 1 (default)

// Instance: increment specific counter instance
const visitsInstance = myObject.get('visits').instance();
await visitsInstance?.increment(5);   // increment by 5
await visitsInstance?.increment();    // increment by 1 (default)
```

## Decrement a counter

Decrement the value of a `LiveCounter` using the `decrement()` method. You can specify an amount to decrement by, or use the default decrement of 1:

```javascript theme={null}
// PathObject: decrement counter at path
await myObject.get('visits').decrement(5);  // decrement by 5
await myObject.get('visits').decrement();   // decrement by 1 (default)

// Instance: decrement specific counter instance
const visitsInstance = myObject.get('visits').instance();
await visitsInstance?.decrement(5);   // decrement by 5
await visitsInstance?.decrement();    // decrement by 1 (default)
```

## Batch multiple operations

Group multiple counter operations into a single atomic message using the `batch()` method. All operations within the batch are sent as one logical unit which succeed or fail together:

```javascript theme={null}
// PathObject: batch operations on counter at path
await myObject.get('visits').batch((ctx) => {
  ctx.increment(5);
  ctx.increment(3);
  ctx.decrement(2);
});

// Instance: batch operations on specific counter instance
const visitsInstance = myObject.get('visits').instance();
await visitsInstance?.batch((ctx) => {
  ctx.increment(10);
  ctx.decrement(5);
});
```

<Aside data-type="further-reading">
  Learn more about [Batch operations](/docs/liveobjects/batch-operations).
</Aside>

## Subscribe to updates

Subscribe to `LiveCounter` updates to receive realtime notifications when the value changes.

`PathObject` subscriptions observe a location and automatically track changes even if the `LiveCounter` instance at that path is replaced. `Instance` subscriptions track a specific `LiveCounter` instance, following it even if it moves in the channel object.

```javascript theme={null}
// PathObject: observe location - tracks changes even if counter instance is replaced
const visits = myObject.get('visits');
const { unsubscribe } = visits.subscribe(() => {
  console.log('Visits:', visits.value());
});

// Later, stop listening to changes
unsubscribe();

// Instance: track specific counter instance - follows it even if moved in object tree
const visitsInstance = myObject.get('visits').instance();
if (visitsInstance) {
  const { unsubscribe } = visitsInstance.subscribe(() => {
    console.log('Visits:', visitsInstance.value());
  });

  // Later, stop listening to changes
  unsubscribe();
}
```

Alternatively, use the `subscribeIterator()` method for an async iterator syntax:

```javascript theme={null}
// PathObject: observe location - tracks changes even if counter instance is replaced
const visits = myObject.get('visits');
for await (const _ of visits.subscribeIterator()) {
  console.log('Visits:', visits.value());

  if (someCondition) {
    break; // Unsubscribes
  }
}

// Instance: track specific counter instance - follows it even if moved in object tree
const visitsInstance = myObject.get('visits').instance();
if (visitsInstance) {
  for await (const _ of visitsInstance.subscribeIterator()) {
    console.log('Visits:', visitsInstance.value());

    if (someCondition) {
      break; // Unsubscribes
    }
  }
}
```
