@tanstack/angular-pacer 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -7
  3. package/dist/async-batcher/index.d.cts +2 -2
  4. package/dist/async-batcher/index.d.ts +2 -2
  5. package/dist/async-batcher/injectAsyncBatcher.cjs +27 -3
  6. package/dist/async-batcher/injectAsyncBatcher.cjs.map +1 -1
  7. package/dist/async-batcher/injectAsyncBatcher.d.cts +25 -3
  8. package/dist/async-batcher/injectAsyncBatcher.d.ts +25 -3
  9. package/dist/async-batcher/injectAsyncBatcher.js +27 -3
  10. package/dist/async-batcher/injectAsyncBatcher.js.map +1 -1
  11. package/dist/async-debouncer/index.d.cts +2 -2
  12. package/dist/async-debouncer/index.d.ts +2 -2
  13. package/dist/async-debouncer/injectAsyncDebouncer.cjs +27 -3
  14. package/dist/async-debouncer/injectAsyncDebouncer.cjs.map +1 -1
  15. package/dist/async-debouncer/injectAsyncDebouncer.d.cts +25 -3
  16. package/dist/async-debouncer/injectAsyncDebouncer.d.ts +25 -3
  17. package/dist/async-debouncer/injectAsyncDebouncer.js +27 -3
  18. package/dist/async-debouncer/injectAsyncDebouncer.js.map +1 -1
  19. package/dist/async-queuer/index.d.cts +2 -2
  20. package/dist/async-queuer/index.d.ts +2 -2
  21. package/dist/async-queuer/injectAsyncQueuedSignal.cjs.map +1 -1
  22. package/dist/async-queuer/injectAsyncQueuedSignal.d.cts +5 -10
  23. package/dist/async-queuer/injectAsyncQueuedSignal.d.ts +5 -10
  24. package/dist/async-queuer/injectAsyncQueuedSignal.js.map +1 -1
  25. package/dist/async-queuer/injectAsyncQueuer.cjs +27 -3
  26. package/dist/async-queuer/injectAsyncQueuer.cjs.map +1 -1
  27. package/dist/async-queuer/injectAsyncQueuer.d.cts +25 -3
  28. package/dist/async-queuer/injectAsyncQueuer.d.ts +25 -3
  29. package/dist/async-queuer/injectAsyncQueuer.js +27 -3
  30. package/dist/async-queuer/injectAsyncQueuer.js.map +1 -1
  31. package/dist/async-rate-limiter/index.d.cts +2 -2
  32. package/dist/async-rate-limiter/index.d.ts +2 -2
  33. package/dist/async-rate-limiter/injectAsyncRateLimiter.cjs +15 -3
  34. package/dist/async-rate-limiter/injectAsyncRateLimiter.cjs.map +1 -1
  35. package/dist/async-rate-limiter/injectAsyncRateLimiter.d.cts +15 -3
  36. package/dist/async-rate-limiter/injectAsyncRateLimiter.d.ts +15 -3
  37. package/dist/async-rate-limiter/injectAsyncRateLimiter.js +15 -3
  38. package/dist/async-rate-limiter/injectAsyncRateLimiter.js.map +1 -1
  39. package/dist/async-throttler/index.d.cts +2 -2
  40. package/dist/async-throttler/index.d.ts +2 -2
  41. package/dist/async-throttler/injectAsyncThrottler.cjs +27 -3
  42. package/dist/async-throttler/injectAsyncThrottler.cjs.map +1 -1
  43. package/dist/async-throttler/injectAsyncThrottler.d.cts +25 -3
  44. package/dist/async-throttler/injectAsyncThrottler.d.ts +25 -3
  45. package/dist/async-throttler/injectAsyncThrottler.js +27 -3
  46. package/dist/async-throttler/injectAsyncThrottler.js.map +1 -1
  47. package/dist/batcher/index.d.cts +2 -2
  48. package/dist/batcher/index.d.ts +2 -2
  49. package/dist/batcher/injectBatcher.cjs +22 -3
  50. package/dist/batcher/injectBatcher.cjs.map +1 -1
  51. package/dist/batcher/injectBatcher.d.cts +22 -3
  52. package/dist/batcher/injectBatcher.d.ts +22 -3
  53. package/dist/batcher/injectBatcher.js +22 -3
  54. package/dist/batcher/injectBatcher.js.map +1 -1
  55. package/dist/debouncer/index.d.cts +2 -2
  56. package/dist/debouncer/index.d.ts +2 -2
  57. package/dist/debouncer/injectDebouncedSignal.cjs.map +1 -1
  58. package/dist/debouncer/injectDebouncedSignal.d.cts +5 -10
  59. package/dist/debouncer/injectDebouncedSignal.d.ts +5 -10
  60. package/dist/debouncer/injectDebouncedSignal.js.map +1 -1
  61. package/dist/debouncer/injectDebouncer.cjs +22 -3
  62. package/dist/debouncer/injectDebouncer.cjs.map +1 -1
  63. package/dist/debouncer/injectDebouncer.d.cts +22 -3
  64. package/dist/debouncer/injectDebouncer.d.ts +22 -3
  65. package/dist/debouncer/injectDebouncer.js +22 -3
  66. package/dist/debouncer/injectDebouncer.js.map +1 -1
  67. package/dist/index.d.cts +11 -11
  68. package/dist/index.d.ts +11 -11
  69. package/dist/queuer/index.d.cts +2 -2
  70. package/dist/queuer/index.d.ts +2 -2
  71. package/dist/queuer/injectQueuedSignal.cjs.map +1 -1
  72. package/dist/queuer/injectQueuedSignal.d.cts +5 -10
  73. package/dist/queuer/injectQueuedSignal.d.ts +5 -10
  74. package/dist/queuer/injectQueuedSignal.js.map +1 -1
  75. package/dist/queuer/injectQueuer.cjs +22 -3
  76. package/dist/queuer/injectQueuer.cjs.map +1 -1
  77. package/dist/queuer/injectQueuer.d.cts +22 -3
  78. package/dist/queuer/injectQueuer.d.ts +22 -3
  79. package/dist/queuer/injectQueuer.js +22 -3
  80. package/dist/queuer/injectQueuer.js.map +1 -1
  81. package/dist/rate-limiter/index.d.cts +2 -2
  82. package/dist/rate-limiter/index.d.ts +2 -2
  83. package/dist/rate-limiter/injectRateLimitedSignal.cjs.map +1 -1
  84. package/dist/rate-limiter/injectRateLimitedSignal.d.cts +5 -10
  85. package/dist/rate-limiter/injectRateLimitedSignal.d.ts +5 -10
  86. package/dist/rate-limiter/injectRateLimitedSignal.js.map +1 -1
  87. package/dist/rate-limiter/injectRateLimiter.cjs +13 -3
  88. package/dist/rate-limiter/injectRateLimiter.cjs.map +1 -1
  89. package/dist/rate-limiter/injectRateLimiter.d.cts +13 -3
  90. package/dist/rate-limiter/injectRateLimiter.d.ts +13 -3
  91. package/dist/rate-limiter/injectRateLimiter.js +13 -3
  92. package/dist/rate-limiter/injectRateLimiter.js.map +1 -1
  93. package/dist/throttler/index.d.cts +2 -2
  94. package/dist/throttler/index.d.ts +2 -2
  95. package/dist/throttler/injectThrottledSignal.cjs.map +1 -1
  96. package/dist/throttler/injectThrottledSignal.d.cts +5 -10
  97. package/dist/throttler/injectThrottledSignal.d.ts +5 -10
  98. package/dist/throttler/injectThrottledSignal.js.map +1 -1
  99. package/dist/throttler/injectThrottler.cjs +22 -3
  100. package/dist/throttler/injectThrottler.cjs.map +1 -1
  101. package/dist/throttler/injectThrottler.d.cts +22 -3
  102. package/dist/throttler/injectThrottler.d.ts +22 -3
  103. package/dist/throttler/injectThrottler.js +22 -3
  104. package/dist/throttler/injectThrottler.js.map +1 -1
  105. package/package.json +15 -15
  106. package/src/async-batcher/injectAsyncBatcher.ts +43 -4
  107. package/src/async-debouncer/injectAsyncDebouncer.ts +43 -4
  108. package/src/async-queuer/injectAsyncQueuedSignal.ts +4 -12
  109. package/src/async-queuer/injectAsyncQueuer.ts +43 -4
  110. package/src/async-rate-limiter/injectAsyncRateLimiter.ts +32 -4
  111. package/src/async-throttler/injectAsyncThrottler.ts +43 -4
  112. package/src/batcher/injectBatcher.ts +39 -4
  113. package/src/debouncer/injectDebouncedSignal.ts +4 -11
  114. package/src/debouncer/injectDebouncer.ts +39 -4
  115. package/src/queuer/injectQueuedSignal.ts +4 -9
  116. package/src/queuer/injectQueuer.ts +39 -4
  117. package/src/rate-limiter/injectRateLimitedSignal.ts +4 -11
  118. package/src/rate-limiter/injectRateLimiter.ts +28 -4
  119. package/src/throttler/injectThrottledSignal.ts +4 -11
  120. package/src/throttler/injectThrottler.ts +39 -4
@@ -1,10 +1,22 @@
1
+ import { DestroyRef, inject } from '@angular/core'
1
2
  import { injectStore } from '@tanstack/angular-store'
2
3
  import { Batcher } from '@tanstack/pacer/batcher'
3
4
  import { injectPacerOptions } from '../provider/pacer-context'
4
5
  import type { Signal } from '@angular/core'
5
- import type { Store } from '@tanstack/store'
6
+ import type { Store } from '@tanstack/angular-store'
6
7
  import type { BatcherOptions, BatcherState } from '@tanstack/pacer/batcher'
7
8
 
9
+ export interface AngularBatcherOptions<
10
+ TValue,
11
+ TSelected = {},
12
+ > extends BatcherOptions<TValue> {
13
+ /**
14
+ * Optional callback invoked when the component is destroyed. Receives the batcher instance.
15
+ * When provided, replaces the default cleanup (cancel); use it to call flush(), cancel(), add logging, etc.
16
+ */
17
+ onUnmount?: (batcher: AngularBatcher<TValue, TSelected>) => void
18
+ }
19
+
8
20
  export interface AngularBatcher<TValue, TSelected = {}> extends Omit<
9
21
  Batcher<TValue>,
10
22
  'store'
@@ -43,6 +55,18 @@ export interface AngularBatcher<TValue, TSelected = {}> extends Omit<
43
55
  * tracking by providing a selector function. This prevents unnecessary updates and gives you
44
56
  * full control over when your component tracks state changes.
45
57
  *
58
+ * ## Cleanup on Destroy
59
+ *
60
+ * By default, the function cancels any pending batch when the component is destroyed.
61
+ * Use the `onUnmount` option to customize this. For example, to flush pending work instead:
62
+ *
63
+ * ```ts
64
+ * const batcher = injectBatcher(fn, {
65
+ * maxSize: 5,
66
+ * onUnmount: (b) => b.flush()
67
+ * });
68
+ * ```
69
+ *
46
70
  * @example
47
71
  * ```ts
48
72
  * // Default behavior - no reactive state subscriptions
@@ -60,20 +84,31 @@ export interface AngularBatcher<TValue, TSelected = {}> extends Omit<
60
84
  */
61
85
  export function injectBatcher<TValue, TSelected = {}>(
62
86
  fn: (items: Array<TValue>) => void,
63
- options: BatcherOptions<TValue> = {},
87
+ options: AngularBatcherOptions<TValue, TSelected> = {},
64
88
  selector: (state: BatcherState<TValue>) => TSelected = () =>
65
89
  ({}) as TSelected,
66
90
  ): AngularBatcher<TValue, TSelected> {
67
91
  const mergedOptions = {
68
92
  ...injectPacerOptions().batcher,
69
93
  ...options,
70
- } as BatcherOptions<TValue>
94
+ } as AngularBatcherOptions<TValue, TSelected>
71
95
 
72
96
  const batcher = new Batcher<TValue>(fn, mergedOptions)
73
97
  const state = injectStore(batcher.store, selector)
74
98
 
75
- return {
99
+ const result = {
76
100
  ...batcher,
77
101
  state,
78
102
  } as AngularBatcher<TValue, TSelected>
103
+
104
+ const destroyRef = inject(DestroyRef, { optional: true })
105
+ destroyRef?.onDestroy(() => {
106
+ if (mergedOptions.onUnmount) {
107
+ mergedOptions.onUnmount(result)
108
+ } else {
109
+ batcher.cancel()
110
+ }
111
+ })
112
+
113
+ return result
79
114
  }
@@ -8,17 +8,10 @@ import type {
8
8
 
9
9
  type Setter<T> = (value: T | ((prev: T) => T)) => void
10
10
 
11
- export type DebouncedSignal<TValue, TSelected = {}> = ((
12
- ...args: []
13
- ) => TValue) & {
14
- /**
15
- * Set or update the debounced value. This calls `debouncer.maybeExecute(...)`.
16
- */
17
- readonly set: Setter<TValue>
18
- /**
19
- * The debouncer instance with additional control methods and state signals.
20
- */
21
- readonly debouncer: AngularDebouncer<Setter<TValue>, TSelected>
11
+ export interface DebouncedSignal<TValue, TSelected = {}> {
12
+ (): TValue
13
+ set: Setter<TValue>
14
+ debouncer: AngularDebouncer<Setter<TValue>, TSelected>
22
15
  }
23
16
 
24
17
  /**
@@ -1,14 +1,26 @@
1
+ import { DestroyRef, inject } from '@angular/core'
1
2
  import { injectStore } from '@tanstack/angular-store'
2
3
  import { Debouncer } from '@tanstack/pacer/debouncer'
3
4
  import { injectPacerOptions } from '../provider/pacer-context'
4
5
  import type { Signal } from '@angular/core'
5
- import type { Store } from '@tanstack/store'
6
+ import type { Store } from '@tanstack/angular-store'
6
7
  import type { AnyFunction } from '@tanstack/pacer/types'
7
8
  import type {
8
9
  DebouncerOptions,
9
10
  DebouncerState,
10
11
  } from '@tanstack/pacer/debouncer'
11
12
 
13
+ export interface AngularDebouncerOptions<
14
+ TFn extends AnyFunction,
15
+ TSelected = {},
16
+ > extends DebouncerOptions<TFn> {
17
+ /**
18
+ * Optional callback invoked when the component is destroyed. Receives the debouncer instance.
19
+ * When provided, replaces the default cleanup (cancel); use it to call flush(), cancel(), add logging, etc.
20
+ */
21
+ onUnmount?: (debouncer: AngularDebouncer<TFn, TSelected>) => void
22
+ }
23
+
12
24
  export interface AngularDebouncer<
13
25
  TFn extends AnyFunction,
14
26
  TSelected = {},
@@ -57,6 +69,18 @@ export interface AngularDebouncer<
57
69
  * - `lastArgs`: The arguments from the most recent call to maybeExecute
58
70
  * - `status`: Current execution status ('disabled' | 'idle' | 'pending')
59
71
  *
72
+ * ## Cleanup on Destroy
73
+ *
74
+ * By default, the function cancels any pending execution when the component is destroyed.
75
+ * Use the `onUnmount` option to customize this. For example, to flush pending work instead:
76
+ *
77
+ * ```ts
78
+ * const debouncer = injectDebouncer(fn, {
79
+ * wait: 500,
80
+ * onUnmount: (d) => d.flush()
81
+ * });
82
+ * ```
83
+ *
60
84
  * @example
61
85
  * ```ts
62
86
  * // Default behavior - no reactive state subscriptions
@@ -84,19 +108,30 @@ export interface AngularDebouncer<
84
108
  */
85
109
  export function injectDebouncer<TFn extends AnyFunction, TSelected = {}>(
86
110
  fn: TFn,
87
- options: DebouncerOptions<TFn>,
111
+ options: AngularDebouncerOptions<TFn, TSelected>,
88
112
  selector: (state: DebouncerState<TFn>) => TSelected = () => ({}) as TSelected,
89
113
  ): AngularDebouncer<TFn, TSelected> {
90
114
  const mergedOptions = {
91
115
  ...injectPacerOptions().debouncer,
92
116
  ...options,
93
- } as DebouncerOptions<TFn>
117
+ } as AngularDebouncerOptions<TFn, TSelected>
94
118
 
95
119
  const debouncer = new Debouncer<TFn>(fn, mergedOptions)
96
120
  const state = injectStore(debouncer.store, selector)
97
121
 
98
- return {
122
+ const result = {
99
123
  ...debouncer,
100
124
  state,
101
125
  } as AngularDebouncer<TFn, TSelected>
126
+
127
+ const destroyRef = inject(DestroyRef, { optional: true })
128
+ destroyRef?.onDestroy(() => {
129
+ if (mergedOptions.onUnmount) {
130
+ mergedOptions.onUnmount(result)
131
+ } else {
132
+ debouncer.cancel()
133
+ }
134
+ })
135
+
136
+ return result
102
137
  }
@@ -3,15 +3,10 @@ import { injectQueuer } from './injectQueuer'
3
3
  import type { AngularQueuer } from './injectQueuer'
4
4
  import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer'
5
5
 
6
- export type QueuedSignal<TValue, TSelected = {}> = (() => Array<TValue>) & {
7
- /**
8
- * Add an item to the queue.
9
- */
10
- readonly addItem: AngularQueuer<TValue, TSelected>['addItem']
11
- /**
12
- * The queuer instance with additional control methods and state signals.
13
- */
14
- readonly queuer: AngularQueuer<TValue, TSelected>
6
+ export interface QueuedSignal<TValue, TSelected = {}> {
7
+ (): Array<TValue>
8
+ addItem: AngularQueuer<TValue, TSelected>['addItem']
9
+ queuer: AngularQueuer<TValue, TSelected>
15
10
  }
16
11
 
17
12
  /**
@@ -1,10 +1,22 @@
1
+ import { DestroyRef, inject } from '@angular/core'
1
2
  import { injectStore } from '@tanstack/angular-store'
2
3
  import { Queuer } from '@tanstack/pacer/queuer'
3
4
  import { injectPacerOptions } from '../provider/pacer-context'
4
5
  import type { Signal } from '@angular/core'
5
- import type { Store } from '@tanstack/store'
6
+ import type { Store } from '@tanstack/angular-store'
6
7
  import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer'
7
8
 
9
+ export interface AngularQueuerOptions<
10
+ TValue,
11
+ TSelected = {},
12
+ > extends QueuerOptions<TValue> {
13
+ /**
14
+ * Optional callback invoked when the component is destroyed. Receives the queuer instance.
15
+ * When provided, replaces the default cleanup (stop); use it to call flush(), stop(), add logging, etc.
16
+ */
17
+ onUnmount?: (queuer: AngularQueuer<TValue, TSelected>) => void
18
+ }
19
+
8
20
  export interface AngularQueuer<TValue, TSelected = {}> extends Omit<
9
21
  Queuer<TValue>,
10
22
  'store'
@@ -62,22 +74,45 @@ export interface AngularQueuer<TValue, TSelected = {}> extends Omit<
62
74
  * // Access the selected state
63
75
  * const { items, isRunning } = queue.state();
64
76
  * ```
77
+ *
78
+ * ## Cleanup on Destroy
79
+ *
80
+ * By default, the function stops the queuer when the component is destroyed.
81
+ * Use the `onUnmount` option to customize this. For example, to flush pending items instead:
82
+ *
83
+ * ```ts
84
+ * const queue = injectQueuer(fn, {
85
+ * started: true,
86
+ * onUnmount: (q) => q.flush()
87
+ * });
88
+ * ```
65
89
  */
66
90
  export function injectQueuer<TValue, TSelected = {}>(
67
91
  fn: (item: TValue) => void,
68
- options: QueuerOptions<TValue> = {},
92
+ options: AngularQueuerOptions<TValue, TSelected> = {},
69
93
  selector: (state: QueuerState<TValue>) => TSelected = () => ({}) as TSelected,
70
94
  ): AngularQueuer<TValue, TSelected> {
71
95
  const mergedOptions = {
72
96
  ...injectPacerOptions().queuer,
73
97
  ...options,
74
- } as QueuerOptions<TValue>
98
+ } as AngularQueuerOptions<TValue, TSelected>
75
99
 
76
100
  const queuer = new Queuer<TValue>(fn, mergedOptions)
77
101
  const state = injectStore(queuer.store, selector)
78
102
 
79
- return {
103
+ const result = {
80
104
  ...queuer,
81
105
  state,
82
106
  } as AngularQueuer<TValue, TSelected>
107
+
108
+ const destroyRef = inject(DestroyRef, { optional: true })
109
+ destroyRef?.onDestroy(() => {
110
+ if (mergedOptions.onUnmount) {
111
+ mergedOptions.onUnmount(result)
112
+ } else {
113
+ queuer.stop()
114
+ }
115
+ })
116
+
117
+ return result
83
118
  }
@@ -8,17 +8,10 @@ import type {
8
8
 
9
9
  type Setter<T> = (value: T | ((prev: T) => T)) => void
10
10
 
11
- export type RateLimitedSignal<TValue, TSelected = {}> = ((
12
- ...args: []
13
- ) => TValue) & {
14
- /**
15
- * Set or update the rate-limited value. This calls `rateLimiter.maybeExecute(...)`.
16
- */
17
- readonly set: Setter<TValue>
18
- /**
19
- * The rate limiter instance with additional control methods and state signals.
20
- */
21
- readonly rateLimiter: AngularRateLimiter<Setter<TValue>, TSelected>
11
+ export interface RateLimitedSignal<TValue, TSelected = {}> {
12
+ (): TValue
13
+ set: Setter<TValue>
14
+ rateLimiter: AngularRateLimiter<Setter<TValue>, TSelected>
22
15
  }
23
16
 
24
17
  /**
@@ -1,14 +1,25 @@
1
+ import { DestroyRef, inject } from '@angular/core'
1
2
  import { injectStore } from '@tanstack/angular-store'
2
3
  import { RateLimiter } from '@tanstack/pacer/rate-limiter'
3
4
  import { injectPacerOptions } from '../provider/pacer-context'
4
5
  import type { Signal } from '@angular/core'
5
- import type { Store } from '@tanstack/store'
6
+ import type { Store } from '@tanstack/angular-store'
6
7
  import type { AnyFunction } from '@tanstack/pacer/types'
7
8
  import type {
8
9
  RateLimiterOptions,
9
10
  RateLimiterState,
10
11
  } from '@tanstack/pacer/rate-limiter'
11
12
 
13
+ export interface AngularRateLimiterOptions<
14
+ TFn extends AnyFunction,
15
+ TSelected = {},
16
+ > extends RateLimiterOptions<TFn> {
17
+ /**
18
+ * Optional callback invoked when the component is destroyed. Receives the rate limiter instance.
19
+ */
20
+ onUnmount?: (rateLimiter: AngularRateLimiter<TFn, TSelected>) => void
21
+ }
22
+
12
23
  export interface AngularRateLimiter<
13
24
  TFn extends AnyFunction,
14
25
  TSelected = {},
@@ -62,6 +73,10 @@ export interface AngularRateLimiter<
62
73
  * - `executionTimes`: Array of timestamps when executions occurred for rate limiting calculations
63
74
  * - `rejectionCount`: Number of function executions that have been rejected due to rate limiting
64
75
  *
76
+ * ## Cleanup on Destroy
77
+ *
78
+ * Use the `onUnmount` option to run a callback when the component is destroyed.
79
+ *
65
80
  * @example
66
81
  * ```ts
67
82
  * // Default behavior - no reactive state subscriptions
@@ -98,19 +113,28 @@ export interface AngularRateLimiter<
98
113
  */
99
114
  export function injectRateLimiter<TFn extends AnyFunction, TSelected = {}>(
100
115
  fn: TFn,
101
- options: RateLimiterOptions<TFn>,
116
+ options: AngularRateLimiterOptions<TFn, TSelected>,
102
117
  selector: (state: RateLimiterState) => TSelected = () => ({}) as TSelected,
103
118
  ): AngularRateLimiter<TFn, TSelected> {
104
119
  const mergedOptions = {
105
120
  ...injectPacerOptions().rateLimiter,
106
121
  ...options,
107
- } as RateLimiterOptions<TFn>
122
+ } as AngularRateLimiterOptions<TFn, TSelected>
108
123
 
109
124
  const rateLimiter = new RateLimiter<TFn>(fn, mergedOptions)
110
125
  const state = injectStore(rateLimiter.store, selector)
111
126
 
112
- return {
127
+ const result = {
113
128
  ...rateLimiter,
114
129
  state,
115
130
  } as AngularRateLimiter<TFn, TSelected>
131
+
132
+ const destroyRef = inject(DestroyRef, { optional: true })
133
+ destroyRef?.onDestroy(() => {
134
+ if (mergedOptions.onUnmount) {
135
+ mergedOptions.onUnmount(result)
136
+ }
137
+ })
138
+
139
+ return result
116
140
  }
@@ -8,17 +8,10 @@ import type {
8
8
 
9
9
  type Setter<T> = (value: T | ((prev: T) => T)) => void
10
10
 
11
- export type ThrottledSignal<TValue, TSelected = {}> = ((
12
- ...args: []
13
- ) => TValue) & {
14
- /**
15
- * Set or update the throttled value. This calls `throttler.maybeExecute(...)`.
16
- */
17
- readonly set: Setter<TValue>
18
- /**
19
- * The throttler instance with additional control methods and state signals.
20
- */
21
- readonly throttler: AngularThrottler<Setter<TValue>, TSelected>
11
+ export interface ThrottledSignal<TValue, TSelected = {}> {
12
+ (): TValue
13
+ set: Setter<TValue>
14
+ throttler: AngularThrottler<Setter<TValue>, TSelected>
22
15
  }
23
16
 
24
17
  /**
@@ -1,14 +1,26 @@
1
+ import { DestroyRef, inject } from '@angular/core'
1
2
  import { injectStore } from '@tanstack/angular-store'
2
3
  import { Throttler } from '@tanstack/pacer/throttler'
3
4
  import { injectPacerOptions } from '../provider/pacer-context'
4
5
  import type { Signal } from '@angular/core'
5
- import type { Store } from '@tanstack/store'
6
+ import type { Store } from '@tanstack/angular-store'
6
7
  import type { AnyFunction } from '@tanstack/pacer/types'
7
8
  import type {
8
9
  ThrottlerOptions,
9
10
  ThrottlerState,
10
11
  } from '@tanstack/pacer/throttler'
11
12
 
13
+ export interface AngularThrottlerOptions<
14
+ TFn extends AnyFunction,
15
+ TSelected = {},
16
+ > extends ThrottlerOptions<TFn> {
17
+ /**
18
+ * Optional callback invoked when the component is destroyed. Receives the throttler instance.
19
+ * When provided, replaces the default cleanup (cancel); use it to call flush(), cancel(), add logging, etc.
20
+ */
21
+ onUnmount?: (throttler: AngularThrottler<TFn, TSelected>) => void
22
+ }
23
+
12
24
  export interface AngularThrottler<
13
25
  TFn extends AnyFunction,
14
26
  TSelected = {},
@@ -58,6 +70,18 @@ export interface AngularThrottler<
58
70
  * - `nextExecutionTime`: Timestamp of the next allowed execution
59
71
  * - `status`: Current execution status ('disabled' | 'idle' | 'pending')
60
72
  *
73
+ * ## Cleanup on Destroy
74
+ *
75
+ * By default, the function cancels any pending execution when the component is destroyed.
76
+ * Use the `onUnmount` option to customize this. For example, to flush pending work instead:
77
+ *
78
+ * ```ts
79
+ * const throttler = injectThrottler(fn, {
80
+ * wait: 100,
81
+ * onUnmount: (t) => t.flush()
82
+ * });
83
+ * ```
84
+ *
61
85
  * @example
62
86
  * ```ts
63
87
  * // Default behavior - no reactive state subscriptions
@@ -84,19 +108,30 @@ export interface AngularThrottler<
84
108
  */
85
109
  export function injectThrottler<TFn extends AnyFunction, TSelected = {}>(
86
110
  fn: TFn,
87
- options: ThrottlerOptions<TFn>,
111
+ options: AngularThrottlerOptions<TFn, TSelected>,
88
112
  selector: (state: ThrottlerState<TFn>) => TSelected = () => ({}) as TSelected,
89
113
  ): AngularThrottler<TFn, TSelected> {
90
114
  const mergedOptions = {
91
115
  ...injectPacerOptions().throttler,
92
116
  ...options,
93
- } as ThrottlerOptions<TFn>
117
+ } as AngularThrottlerOptions<TFn, TSelected>
94
118
 
95
119
  const throttler = new Throttler<TFn>(fn, mergedOptions)
96
120
  const state = injectStore(throttler.store, selector)
97
121
 
98
- return {
122
+ const result = {
99
123
  ...throttler,
100
124
  state,
101
125
  } as AngularThrottler<TFn, TSelected>
126
+
127
+ const destroyRef = inject(DestroyRef, { optional: true })
128
+ destroyRef?.onDestroy(() => {
129
+ if (mergedOptions.onUnmount) {
130
+ mergedOptions.onUnmount(result)
131
+ } else {
132
+ throttler.cancel()
133
+ }
134
+ })
135
+
136
+ return result
102
137
  }