# Angular Queuing Guide

Queuing stores operations in an ordered buffer and processes them individually. It is the primary Pacer strategy for work that should not be discarded when calls arrive faster than they can run.

Queues are lossless only while they have capacity. A finite `maxSize`, explicit clearing, expiration, or a processing error can still remove or reject work.

## How queuing works

```text
Queuing (process one item every 2 ticks)
Timeline: [1 second per tick]
Calls:        ⬇️  ⬇️  ⬇️     ⬇️  ⬇️     ⬇️  ⬇️  ⬇️
Queue:       [ABC]   [BC]    [BCDE]    [DE]    [E]    []
Executed:     ✅     ✅       ✅        ✅      ✅     ✅
             [======================================================]
             ^ Accepted items remain queued until processed

             [Items arrive]    [Process steadily]      [Empty]
```

The queue can process automatically with a delay between items, or remain stopped for manual processing.

## When to use queuing

Choose queuing when:

- Every accepted operation should run individually.
- Processing order matters.
- Incoming work may temporarily exceed processing capacity.
- A maximum buffer size should reject excess work explicitly.
- FIFO, LIFO, or priority ordering is required.

Choose another utility when:

- Only the final call matters. Use [debouncing](./debouncing.md).
- Intermediate calls may be discarded while work runs steadily. Use [throttling](./throttling.md).
- Calls should be rejected after a time-window quota. Use [rate limiting](./rate-limiting.md).
- Items should run together. Use [batching](./batching.md).
- Tasks return Promises or should run concurrently. Use [async queuing](./async-queuing.md).

## Choose an API

- `injectQueuedValue` to queue observed source values
- `injectQueuedSignal` to queue individual `set` and `update` operations
- `injectQueuerItems` for a signal of pending queue items
- `injectQueuer` for direct queue lifecycle and ordering control

Use the items helper when queue contents drive the UI. Use the instance API for ordering, capacity, expiration, pause, resume, flush, and manual processing.

## Angular example

```ts
import { injectQueuerItems } from '@tanstack/angular-pacer'

export class JobQueueComponent {
  readonly queued = injectQueuerItems(processJob, { wait: 500 })

  add() {
    this.queued.addItem(nextJob())
  }
}
```

The focused snippets later in this guide use `injectQueuer` and assume they run inside an Angular injection context.

Pass `initialItems` when work is already available at creation time. The queue applies its normal insertion and capacity rules, and automatic processing can begin immediately unless `started: false` is set.

## Reading pending items

`queued()` returns the waiting items as a readonly array. Use the `addItem` shortcut to enqueue items and `queued.queuer` to control processing:

```ts
const queued = injectQueuerItems(processItem, { started: false })
queued.addItem('first')
queued.addItem('second')
console.log(queued()) // ['first', 'second']
queued.queuer.start()
```

Pass a third-argument selector to expose counters or running state through `queued.queuer.state()`. The items signal still returns the waiting items.

## Queued values

`injectQueuedValue` returns the last processed source value. It queues the initial value and subsequent values observed by Angular effects. Multiple source writes before an effect runs are observed as one change.

```ts
const source = signal('')
const queued = injectQueuedValue(source, { wait: 500 })
source.set('next')
console.log(queued()) // Last processed value.
```

`injectQueuedSignal` returns an editable signal whose accepted writes are queued individually. Each updater runs against the value at processing time:

```ts
const count = injectQueuedSignal(0, { started: false })
count.set(1)
count.update((previous) => previous + 2)
count.queuer.flush()
console.log(count()) // 3
```

Both helpers accept a selector for their attached `queuer.state()`. The editable helper queues replacement values or updater functions. Updaters are evaluated against the committed value when processed, matching the delayed setter behavior of React and Solid.

## Ordering items

Automatic processing uses `addItemsTo` to choose where new items enter and `getItemsFrom` to choose where items leave.

### FIFO

FIFO processes the oldest item first. This is the default.

```ts
const queuer = injectQueuer(processItem, {
  addItemsTo: 'back',
  getItemsFrom: 'front',
  started: false,
})

queuer.addItem(1)
queuer.addItem(2)
queuer.addItem(3)
queuer.start() // Processes 1, 2, 3.
```

### LIFO

LIFO processes the newest item first.

```ts
const queuer = injectQueuer(processItem, {
  addItemsTo: 'back',
  getItemsFrom: 'back',
  started: false,
})

queuer.addItem(1)
queuer.addItem(2)
queuer.addItem(3)
queuer.start() // Processes 3, 2, 1.
```

### Priority

Provide `getPriority` to process higher numeric priorities first. Priority ordering takes precedence over front and back retrieval.

```ts
type Task = { name: string; priority: number }

const queuer = injectQueuer<Task>(processTask, {
  getPriority: (task) => task.priority,
  started: false,
})

queuer.addItem({ name: 'low', priority: 1 })
queuer.addItem({ name: 'high', priority: 3 })
queuer.addItem({ name: 'medium', priority: 2 })
queuer.start() // Processes high, medium, low.
```

## Automatic and manual processing

Queues start automatically by default. The first accepted item processes immediately, then `wait` controls the delay before later items.

```ts
const queuer = injectQueuer(processItem, {
  wait: 1000,
})
```

Set `started: false` to collect items before processing:

```ts
const queuer = injectQueuer(processItem, { started: false })

queuer.addItem(1)
queuer.addItem(2)
queuer.start()
queuer.stop()
```

`stop()` cancels the scheduled tick and retains queued items. `start()` resumes automatic processing.

For manual control:

- `execute()` removes and processes the next item immediately.
- `getNextItem()` removes and returns the next item without processing it.
- `peekNextItem()` returns the next item without removing it.
- `peekAllItems()` returns a copy of the current queue.

## Capacity and rejection

Set `maxSize` to bound the number of waiting items. An item added to a full queue is rejected, `addItem()` returns `false`, and `onReject` runs.

```ts
const queuer = injectQueuer(processItem, {
  maxSize: 2,
  started: false,
  onReject: (item, queuer) => {
    console.log('Rejected:', item)
    console.log('Total rejections:', queuer.store.state.rejectionCount)
  },
})

queuer.addItem(1) // true
queuer.addItem(2) // true
queuer.addItem(3) // false
```

The active synchronous execution is not part of `size`; `size` counts items still waiting in the queue.

## Expiring stale items

Use `expirationDuration` to remove items that have waited too long:

```ts
const queuer = injectQueuer(processItem, {
  expirationDuration: 5000,
  onExpire: (item) => {
    console.log('Expired:', item)
  },
})
```

Use `getIsExpired` for custom logic:

```ts
const queuer = injectQueuer(processItem, {
  getIsExpired: (item, addedAt) => Date.now() - addedAt > item.maxAge,
})
```

Expiration is checked while the automatic processing loop runs. A stopped queue evaluates stale items when processing resumes.

## Flushing, clearing, and resetting

### Flush

`flush()` processes waiting items immediately without the configured delay. Pass a count to process only part of the queue.

```ts
queuer.flush() // Process all waiting items.
queuer.flush(2) // Process at most two waiting items.
```

`flushAsBatch()` removes all waiting items and passes them to a separate batch function:

```ts
queuer.flushAsBatch((items) => {
  saveItems(items)
})
```

### Clear

`clear()` removes all waiting items without processing them. It does not change whether the queue is running.

```ts
queuer.clear()
```

### Reset

`reset()` restores state to the default running, empty queue. It does not clear an already scheduled timeout. Call `stop()` before `reset()` when scheduled work must be canceled.

```ts
queuer.stop()
queuer.reset()
```

## Configuring and observing the queue

Use an options function to read reactive configuration:

```ts
import { signal } from '@angular/core'

const options = signal({ wait: 500, maxSize: 10 })
const queuer = injectQueuer(processItem, options)

options.set({ wait: 250, maxSize: 20 })
```

The `wait` option may be a function that receives the queuer instance:

```ts
const queuer = injectQueuer(processItem, {
  wait: (queuer) => (queuer.store.state.size > 20 ? 50 : 250),
})
```

Use callbacks for queue events:

- `onItemsChange`: An item was added or removed.
- `onExecute`: An item was processed.
- `onReject`: An item was rejected.
- `onExpire`: An item expired.

## Angular lifecycle

The adapter stops automatic processing when its owner is destroyed. Providing `onUnmount` replaces that default cleanup, so a custom callback must perform every required lifecycle action. When custom cleanup flushes work, remember that user callbacks can run while the component is being destroyed.

## Reactive state

Pass a selector to expose the state used by your view through `state()`. Without a selector, `state()` returns `{}`:

```ts
const queuer = injectQueuer(processItem, { wait: 250 }, (state) => ({
  size: state.size,
  isRunning: state.isRunning,
}))

console.log(queuer.state().size, queuer.state().isRunning)
```

Option callbacks receive the core utility instance. Read the selected `state()` signal in templates.

`initialState` can restore selected queue state that your app has persisted. If it includes `items`, they take precedence over `initialItems`; `initialState.isRunning` likewise takes precedence over `started`. Restore only durable fields. Pending timers are not restored.

Commonly useful state includes:

- `items` and `size`: Items still waiting.
- `isRunning`: Whether automatic processing is enabled.
- `isIdle`: Whether a running queue is empty.
- `isFull`: Whether `maxSize` has been reached.
- `executionCount`: Items whose wrapped function returned successfully.
- `rejectionCount` and `expirationCount`: Items removed without processing.
- `status`: `'idle'`, `'running'`, or `'stopped'`.

See the [Angular API reference](../reference/index.md) for adapter signatures and the public core reference for complete option and state types.
