From dd30751a800821565ac8d4cb226371e75d382e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Sun, 22 Feb 2026 16:34:40 -0300 Subject: [PATCH 1/6] test: add angular required input reproduction --- packages/angular-pacer/package.json | 6 +- .../tests/queuer/injectQueuedValue.spec.ts | 38 +++++++++ packages/angular-pacer/tests/test-setup.ts | 5 ++ packages/angular-pacer/tsconfig.base.json | 3 + packages/angular-pacer/tsconfig.json | 11 ++- packages/angular-pacer/tsconfig.lib.json | 5 ++ packages/angular-pacer/tsconfig.spec.json | 11 +++ packages/angular-pacer/tsdown.config.ts | 1 + packages/angular-pacer/vitest.config.ts | 5 +- pnpm-lock.yaml | 77 +++++++++++++++++++ 10 files changed, 154 insertions(+), 8 deletions(-) create mode 100644 packages/angular-pacer/tests/queuer/injectQueuedValue.spec.ts create mode 100644 packages/angular-pacer/tests/test-setup.ts create mode 100644 packages/angular-pacer/tsconfig.base.json create mode 100644 packages/angular-pacer/tsconfig.lib.json create mode 100644 packages/angular-pacer/tsconfig.spec.json diff --git a/packages/angular-pacer/package.json b/packages/angular-pacer/package.json index 3498206d..1ca62071 100644 --- a/packages/angular-pacer/package.json +++ b/packages/angular-pacer/package.json @@ -112,8 +112,12 @@ "@tanstack/pacer": "workspace:*" }, "devDependencies": { + "@analogjs/vite-plugin-angular": "^2.2.3", + "@analogjs/vitest-angular": "^2.2.3", + "@angular/compiler": "^21.1.4", "@angular/core": "^21.1.4", - "@types/node": "^25.2.3" + "@types/node": "^25.2.3", + "jsdom": "^28.1.0" }, "peerDependencies": { "@angular/core": ">=17.0.0" diff --git a/packages/angular-pacer/tests/queuer/injectQueuedValue.spec.ts b/packages/angular-pacer/tests/queuer/injectQueuedValue.spec.ts new file mode 100644 index 00000000..a998396d --- /dev/null +++ b/packages/angular-pacer/tests/queuer/injectQueuedValue.spec.ts @@ -0,0 +1,38 @@ +import { Component, input } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { injectQueuedValue } from '../../src/queuer/injectQueuedValue' + +describe('injectQueuedValue', () => { + describe("with input signals", () => { + @Component({ + selector: 'pacer-test-child', + standalone: true, + template: '', + }) + class ChildComponent { + readonly value = input.required() + readonly queued = injectQueuedValue(this.value, { wait: 0 }) + } + + @Component({ + selector: 'pacer-test-host', + standalone: true, + imports: [ChildComponent], + template: '', + }) + class HostComponent { } + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [HostComponent], + }).compileComponents() + }) + + it('does not throw when used with input.required() during component initialization', () => { + expect(() => { + const fixture = TestBed.createComponent(HostComponent) + fixture.detectChanges() + }).not.toThrow() + }) + }) +}) \ No newline at end of file diff --git a/packages/angular-pacer/tests/test-setup.ts b/packages/angular-pacer/tests/test-setup.ts new file mode 100644 index 00000000..046722c7 --- /dev/null +++ b/packages/angular-pacer/tests/test-setup.ts @@ -0,0 +1,5 @@ +import '@angular/compiler' +import '@analogjs/vitest-angular/setup-snapshots' +import { setupTestBed } from '@analogjs/vitest-angular/setup-testbed' + +setupTestBed() diff --git a/packages/angular-pacer/tsconfig.base.json b/packages/angular-pacer/tsconfig.base.json new file mode 100644 index 00000000..4082f16a --- /dev/null +++ b/packages/angular-pacer/tsconfig.base.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/angular-pacer/tsconfig.json b/packages/angular-pacer/tsconfig.json index 66baec5e..b64539f8 100644 --- a/packages/angular-pacer/tsconfig.json +++ b/packages/angular-pacer/tsconfig.json @@ -1,8 +1,7 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "jsx": "preserve" - }, - "include": ["src", "vitest.config.ts", "tests"], - "exclude": ["eslint.config.js"] + "files": [], + "references": [ + { "path": "./tsconfig.lib.json" }, + { "path": "./tsconfig.spec.json" } + ] } diff --git a/packages/angular-pacer/tsconfig.lib.json b/packages/angular-pacer/tsconfig.lib.json new file mode 100644 index 00000000..19aa5520 --- /dev/null +++ b/packages/angular-pacer/tsconfig.lib.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.base.json", + "include": ["src", "vitest.config.ts"], + "exclude": ["eslint.config.js", "tests"] +} diff --git a/packages/angular-pacer/tsconfig.spec.json b/packages/angular-pacer/tsconfig.spec.json new file mode 100644 index 00000000..07715c85 --- /dev/null +++ b/packages/angular-pacer/tsconfig.spec.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": false, + "target": "ES2022", + "types": ["vitest/globals", "node"] + }, + "files": ["tests/test-setup.ts"], + "include": ["tests/**/*.spec.ts", "tests/**/*.d.ts"], + "references": [{ "path": "./tsconfig.lib.json" }] +} diff --git a/packages/angular-pacer/tsdown.config.ts b/packages/angular-pacer/tsdown.config.ts index 24dd8a5c..bbd20035 100644 --- a/packages/angular-pacer/tsdown.config.ts +++ b/packages/angular-pacer/tsdown.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'tsdown' export default defineConfig({ + tsconfig: './tsconfig.lib.json', entry: [ './src/index.ts', './src/async-batcher/index.ts', diff --git a/packages/angular-pacer/vitest.config.ts b/packages/angular-pacer/vitest.config.ts index 514aa720..3d33b308 100644 --- a/packages/angular-pacer/vitest.config.ts +++ b/packages/angular-pacer/vitest.config.ts @@ -1,12 +1,15 @@ import { defineConfig } from 'vitest/config' +import angular from '@analogjs/vite-plugin-angular' import packageJson from './package.json' with { type: 'json' } export default defineConfig({ + plugins: [angular()], test: { name: packageJson.name, dir: './tests', watch: false, - environment: 'happy-dom', + environment: 'jsdom', globals: true, + setupFiles: ['./tests/test-setup.ts'], }, }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7ec07715..9206ff8a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4316,12 +4316,24 @@ importers: specifier: workspace:* version: link:../pacer devDependencies: + '@analogjs/vite-plugin-angular': + specifier: ^2.2.3 + version: 2.2.3(@angular/build@21.1.4(@angular/compiler-cli@21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3))(@angular/compiler@21.1.4)(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2))(@angular/platform-browser@21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)))(@types/node@25.2.3)(chokidar@5.0.0)(jiti@2.6.1)(postcss@8.5.6)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.0.18(@types/node@25.2.3)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@28.1.0)(sass@1.97.1)(yaml@2.8.2))(yaml@2.8.2)) + '@analogjs/vitest-angular': + specifier: ^2.2.3 + version: 2.2.3(@analogjs/vite-plugin-angular@2.2.3(@angular/build@21.1.4(@angular/compiler-cli@21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3))(@angular/compiler@21.1.4)(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2))(@angular/platform-browser@21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)))(@types/node@25.2.3)(chokidar@5.0.0)(jiti@2.6.1)(postcss@8.5.6)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.0.18(@types/node@25.2.3)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@28.1.0)(sass@1.97.1)(yaml@2.8.2))(yaml@2.8.2)))(@angular-devkit/architect@0.2101.4(chokidar@5.0.0))(vitest@4.0.18(@types/node@25.2.3)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@28.1.0)(sass@1.97.1)(yaml@2.8.2)) + '@angular/compiler': + specifier: ^21.1.4 + version: 21.1.4 '@angular/core': specifier: ^21.1.4 version: 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2) '@types/node': specifier: ^25.2.3 version: 25.2.3 + jsdom: + specifier: ^28.1.0 + version: 28.1.0 packages/pacer: dependencies: @@ -4572,6 +4584,24 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@analogjs/vite-plugin-angular@2.2.3': + resolution: {integrity: sha512-OqVfiJsaHdHMxzvK0heVvp8MenSXh+xib6/p+v3d44kJ3J7ooD4gRx/jKC350zkgRKwcZc3a0ybGUnG6LEF7mg==} + peerDependencies: + '@angular-devkit/build-angular': ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 + '@angular/build': ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 + peerDependenciesMeta: + '@angular-devkit/build-angular': + optional: true + '@angular/build': + optional: true + + '@analogjs/vitest-angular@2.2.3': + resolution: {integrity: sha512-514A8RqT4sQnWj/3pHnoGrvDLYjcUTWL3d00GCQ4MVRUpisUg2R8tC/PZ/3ZARK5SskIwy5LwPEg4RFX0iOSug==} + peerDependencies: + '@analogjs/vite-plugin-angular': '*' + '@angular-devkit/architect': '>=0.1500.0 < 0.2200.0' + vitest: ^1.3.1 || ^2.0.0 || ^3.0.0 || ^4.0.0 + '@angular-devkit/architect@0.2101.4': resolution: {integrity: sha512-3yyebORk+ovtO+LfDjIGbGCZhCMDAsyn9vkCljARj3sSshS4blOQBar0g+V3kYAweLT5Gf+rTKbN5jneOkBAFQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} @@ -6721,6 +6751,9 @@ packages: peerDependencies: preact: '>=10 || ^10.0.0-alpha.0 || ^10.0.0-beta.0' + '@ts-morph/common@0.22.0': + resolution: {integrity: sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==} + '@tufjs/canonical-json@2.0.0': resolution: {integrity: sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==} engines: {node: ^16.14.0 || >=18.0.0} @@ -7320,6 +7353,9 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + code-block-writer@12.0.0: + resolution: {integrity: sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -8622,6 +8658,11 @@ packages: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -8886,6 +8927,9 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -9560,6 +9604,9 @@ packages: peerDependencies: typescript: '>=4.0.0' + ts-morph@21.0.1: + resolution: {integrity: sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg==} + ts-pattern@5.9.0: resolution: {integrity: sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==} @@ -10139,6 +10186,18 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 + '@analogjs/vite-plugin-angular@2.2.3(@angular/build@21.1.4(@angular/compiler-cli@21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3))(@angular/compiler@21.1.4)(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2))(@angular/platform-browser@21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)))(@types/node@25.2.3)(chokidar@5.0.0)(jiti@2.6.1)(postcss@8.5.6)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.0.18(@types/node@25.2.3)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@28.1.0)(sass@1.97.1)(yaml@2.8.2))(yaml@2.8.2))': + dependencies: + ts-morph: 21.0.1 + optionalDependencies: + '@angular/build': 21.1.4(@angular/compiler-cli@21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3))(@angular/compiler@21.1.4)(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2))(@angular/platform-browser@21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)))(@types/node@25.2.3)(chokidar@5.0.0)(jiti@2.6.1)(postcss@8.5.6)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.0.18(@types/node@25.2.3)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@28.1.0)(sass@1.97.1)(yaml@2.8.2))(yaml@2.8.2) + + '@analogjs/vitest-angular@2.2.3(@analogjs/vite-plugin-angular@2.2.3(@angular/build@21.1.4(@angular/compiler-cli@21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3))(@angular/compiler@21.1.4)(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2))(@angular/platform-browser@21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)))(@types/node@25.2.3)(chokidar@5.0.0)(jiti@2.6.1)(postcss@8.5.6)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.0.18(@types/node@25.2.3)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@28.1.0)(sass@1.97.1)(yaml@2.8.2))(yaml@2.8.2)))(@angular-devkit/architect@0.2101.4(chokidar@5.0.0))(vitest@4.0.18(@types/node@25.2.3)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@28.1.0)(sass@1.97.1)(yaml@2.8.2))': + dependencies: + '@analogjs/vite-plugin-angular': 2.2.3(@angular/build@21.1.4(@angular/compiler-cli@21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3))(@angular/compiler@21.1.4)(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2))(@angular/platform-browser@21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)))(@types/node@25.2.3)(chokidar@5.0.0)(jiti@2.6.1)(postcss@8.5.6)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.0.18(@types/node@25.2.3)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@28.1.0)(sass@1.97.1)(yaml@2.8.2))(yaml@2.8.2)) + '@angular-devkit/architect': 0.2101.4(chokidar@5.0.0) + vitest: 4.0.18(@types/node@25.2.3)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@28.1.0)(sass@1.97.1)(yaml@2.8.2) + '@angular-devkit/architect@0.2101.4(chokidar@5.0.0)': dependencies: '@angular-devkit/core': 21.1.4(chokidar@5.0.0) @@ -12175,6 +12234,13 @@ snapshots: '@testing-library/dom': 8.20.1 preact: 10.28.3 + '@ts-morph/common@0.22.0': + dependencies: + fast-glob: 3.3.3 + minimatch: 9.0.5 + mkdirp: 3.0.1 + path-browserify: 1.0.1 + '@tufjs/canonical-json@2.0.0': {} '@tufjs/models@4.1.0': @@ -12815,6 +12881,8 @@ snapshots: clsx@2.1.1: {} + code-block-writer@12.0.0: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -14270,6 +14338,8 @@ snapshots: dependencies: minipass: 7.1.2 + mkdirp@3.0.1: {} + mri@1.2.0: {} mrmime@2.0.1: {} @@ -14647,6 +14717,8 @@ snapshots: parseurl@1.3.3: {} + path-browserify@1.0.1: {} + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -15351,6 +15423,11 @@ snapshots: picomatch: 4.0.3 typescript: 5.9.3 + ts-morph@21.0.1: + dependencies: + '@ts-morph/common': 0.22.0 + code-block-writer: 12.0.0 + ts-pattern@5.9.0: {} tsconfig-paths@4.2.0: From d6696fe60d830f2845753c3f4722ba6ef71294c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Sun, 22 Feb 2026 17:11:45 -0300 Subject: [PATCH 2/6] fix: call value inside linked signal --- packages/angular-pacer/src/queuer/injectQueuedValue.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/angular-pacer/src/queuer/injectQueuedValue.ts b/packages/angular-pacer/src/queuer/injectQueuedValue.ts index 9eff79c0..7fffdc7f 100644 --- a/packages/angular-pacer/src/queuer/injectQueuedValue.ts +++ b/packages/angular-pacer/src/queuer/injectQueuedValue.ts @@ -69,10 +69,6 @@ export function injectQueuedValue< const hasInitialValue = (initialOptionsOrSelector !== undefined && !hasSelector) || maybeSelector !== undefined - - const initialValue = hasInitialValue - ? (initialValueOrOptions as TValue) - : value() const initialOptions = hasInitialValue ? (initialOptionsOrSelector as QueuerOptions) : (initialValueOrOptions as QueuerOptions) @@ -83,7 +79,11 @@ export function injectQueuedValue< | undefined) const linkedValue = linkedSignal(() => value()) - const queuedValue = signal(initialValue) + const queuedValue = linkedSignal(() => { + return hasInitialValue + ? (initialValueOrOptions as TValue) + : untracked(value) + }) const queued = injectQueuedSignal( (item) => { From adf87149c2d369636f9070876e3d140e9826189f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Sun, 22 Feb 2026 17:25:56 -0300 Subject: [PATCH 3/6] fix: align angular queued value api --- .../injectQueuedValue/src/app/app.html | 2 +- .../angular/injectQueuedValue/src/app/app.ts | 2 +- .../injectQueuedValue/src/app/inputapp.ts | 2 +- .../src/queuer/injectQueuedValue.ts | 28 ++++++--- .../tests/queuer/injectQueuedValue.spec.ts | 62 ++++++++++++++++++- 5 files changed, 80 insertions(+), 16 deletions(-) diff --git a/examples/angular/injectQueuedValue/src/app/app.html b/examples/angular/injectQueuedValue/src/app/app.html index ad483d97..b445f9c8 100644 --- a/examples/angular/injectQueuedValue/src/app/app.html +++ b/examples/angular/injectQueuedValue/src/app/app.html @@ -4,7 +4,7 @@

injectQueuedValue

Source (instant): {{ source() }}

Value (queued): {{ queued() }}

-

Queue length: {{ queued().length }}

+

Queue length: {{ queued.queuer.state().items.length }}

diff --git a/examples/angular/injectQueuedValue/src/app/app.ts b/examples/angular/injectQueuedValue/src/app/app.ts index c1e04c46..7c48b697 100644 --- a/examples/angular/injectQueuedValue/src/app/app.ts +++ b/examples/angular/injectQueuedValue/src/app/app.ts @@ -12,7 +12,7 @@ export class App { protected readonly source = signal('') // A queued value: changes are applied in-order, with an optional delay between items. - // `value()` is the current processed value, and `items()` exposes the pending queue. + // `queued()` is the current processed value, and `queued.queuer.state().items` exposes the pending queue. protected readonly queued = injectQueuedValue(this.source, { wait: 500 }, (state) => ({ items: state.items, })) diff --git a/examples/angular/injectQueuedValue/src/app/inputapp.ts b/examples/angular/injectQueuedValue/src/app/inputapp.ts index 978d1cb7..96341b6c 100644 --- a/examples/angular/injectQueuedValue/src/app/inputapp.ts +++ b/examples/angular/injectQueuedValue/src/app/inputapp.ts @@ -8,7 +8,7 @@ import { injectQueuedValue } from '@tanstack/angular-pacer'

NG0950

value: {{ value() }}

Value (queued): {{ queued() }}

-

Queue length: {{ queued().length }}

+

Queue length: {{ queued.queuer.state().items.length }}

`, }) diff --git a/packages/angular-pacer/src/queuer/injectQueuedValue.ts b/packages/angular-pacer/src/queuer/injectQueuedValue.ts index 7fffdc7f..8dde185b 100644 --- a/packages/angular-pacer/src/queuer/injectQueuedValue.ts +++ b/packages/angular-pacer/src/queuer/injectQueuedValue.ts @@ -1,8 +1,14 @@ -import { effect, linkedSignal, signal } from '@angular/core' +import { effect, linkedSignal, untracked } from '@angular/core' import { injectQueuedSignal } from './injectQueuedSignal' -import type { QueuedSignal } from './injectQueuedSignal' import type { Signal } from '@angular/core' import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' +import { AngularQueuer } from './injectQueuer' + +export interface QueuedValueSignal { + (): TValue + addItem: AngularQueuer['addItem'] + queuer: AngularQueuer +} /** * An Angular function that creates a queued value that processes state changes in order with an optional delay. @@ -38,7 +44,7 @@ export function injectQueuedValue< value: Signal, options?: QueuerOptions, selector?: (state: QueuerState) => TSelected, -): QueuedSignal +): QueuedValueSignal export function injectQueuedValue< TValue, TSelected extends Pick, 'items'> = Pick< @@ -50,7 +56,7 @@ export function injectQueuedValue< initialValue: TValue, options?: QueuerOptions, selector?: (state: QueuerState) => TSelected, -): QueuedSignal +): QueuedValueSignal export function injectQueuedValue< TValue, TSelected extends Pick, 'items'> = Pick< @@ -64,7 +70,7 @@ export function injectQueuedValue< | QueuerOptions | ((state: QueuerState) => TSelected), maybeSelector?: (state: QueuerState) => TSelected, -): QueuedSignal { +): QueuedValueSignal { const hasSelector = typeof initialOptionsOrSelector === 'function' const hasInitialValue = (initialOptionsOrSelector !== undefined && !hasSelector) || @@ -75,10 +81,9 @@ export function injectQueuedValue< const selector = hasInitialValue ? maybeSelector : (initialOptionsOrSelector as - | ((state: QueuerState) => TSelected) - | undefined) + | ((state: QueuerState) => TSelected) + | undefined) - const linkedValue = linkedSignal(() => value()) const queuedValue = linkedSignal(() => { return hasInitialValue ? (initialValueOrOptions as TValue) @@ -94,8 +99,11 @@ export function injectQueuedValue< ) effect(() => { - queued.addItem(linkedValue()) + queued.addItem(value()) }) - return queued + return Object.assign(queuedValue, { + addItem: queued.addItem.bind(queued), + queuer: queued.queuer, + }) as QueuedValueSignal } diff --git a/packages/angular-pacer/tests/queuer/injectQueuedValue.spec.ts b/packages/angular-pacer/tests/queuer/injectQueuedValue.spec.ts index a998396d..f2104901 100644 --- a/packages/angular-pacer/tests/queuer/injectQueuedValue.spec.ts +++ b/packages/angular-pacer/tests/queuer/injectQueuedValue.spec.ts @@ -1,9 +1,65 @@ -import { Component, input } from '@angular/core' +import { Component, input, signal } from '@angular/core' import { TestBed } from '@angular/core/testing' +import { vi } from 'vitest' import { injectQueuedValue } from '../../src/queuer/injectQueuedValue' +beforeEach(() => { + vi.useFakeTimers() +}) + +afterEach(() => { + vi.useRealTimers() +}) + describe('injectQueuedValue', () => { - describe("with input signals", () => { + describe('behaviour', () => { + it('returns a queued signal with addItem and queuer', () => { + const value = signal('initial') + const queued = TestBed.runInInjectionContext(() => + injectQueuedValue(value, { + wait: 0, + }), + ) + expect(typeof queued).toBe('function') + expect(queued.addItem).toBeDefined() + expect(queued.queuer).toBeDefined() + }) + + it('pushes source signal value into the queue when it changes', () => { + const value = signal('initial') + const queued = TestBed.runInInjectionContext(() => + injectQueuedValue(value, { + wait: 0, + }), + ) + TestBed.tick() + expect(queued()).toBe('initial') + value.set('second') + TestBed.tick() + expect(queued()).toBe('second') + }) + + it('waits for the wait time before processing the next item', () => { + const value = signal('initial') + const queued = TestBed.runInInjectionContext(() => + injectQueuedValue(value, { + wait: 1000, + }), + ) + TestBed.tick() + value.set('second') + TestBed.tick() + value.set('third') + TestBed.tick() + expect(queued()).toBe('initial') + vi.advanceTimersByTime(1000) + expect(queued()).toBe('second') + vi.advanceTimersByTime(1000) + expect(queued()).toBe('third') + }) + }) + + describe('with input signals', () => { @Component({ selector: 'pacer-test-child', standalone: true, @@ -35,4 +91,4 @@ describe('injectQueuedValue', () => { }).not.toThrow() }) }) -}) \ No newline at end of file +}) From c14701d7fa89ad2385821c4586faf17ea486a0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Sun, 22 Feb 2026 17:51:11 -0300 Subject: [PATCH 4/6] fix: linting with new angular ts config --- packages/angular-pacer/eslint.config.js | 17 ++++++++++++++++- .../src/queuer/injectQueuedValue.ts | 6 +++--- .../tests/queuer/injectQueuedValue.spec.ts | 2 +- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/angular-pacer/eslint.config.js b/packages/angular-pacer/eslint.config.js index 50a11bbd..c5af2db8 100644 --- a/packages/angular-pacer/eslint.config.js +++ b/packages/angular-pacer/eslint.config.js @@ -1,6 +1,21 @@ // @ts-check import rootConfig from '../../eslint.config.js' +import { fileURLToPath } from 'node:url' + +const tsconfigRootDir = fileURLToPath(new URL('.', import.meta.url)) /** @type {import('eslint').Linter.Config[]} */ -export default [...rootConfig] +export default [ + ...rootConfig, + { + name: 'angular-pacer/typescript-projects', + files: ['**/*.ts'], + languageOptions: { + parserOptions: { + project: ['./tsconfig.lib.json', './tsconfig.spec.json'], + tsconfigRootDir, + }, + }, + }, +] diff --git a/packages/angular-pacer/src/queuer/injectQueuedValue.ts b/packages/angular-pacer/src/queuer/injectQueuedValue.ts index 8dde185b..e65e27e9 100644 --- a/packages/angular-pacer/src/queuer/injectQueuedValue.ts +++ b/packages/angular-pacer/src/queuer/injectQueuedValue.ts @@ -2,7 +2,7 @@ import { effect, linkedSignal, untracked } from '@angular/core' import { injectQueuedSignal } from './injectQueuedSignal' import type { Signal } from '@angular/core' import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' -import { AngularQueuer } from './injectQueuer' +import type { AngularQueuer } from './injectQueuer' export interface QueuedValueSignal { (): TValue @@ -81,8 +81,8 @@ export function injectQueuedValue< const selector = hasInitialValue ? maybeSelector : (initialOptionsOrSelector as - | ((state: QueuerState) => TSelected) - | undefined) + | ((state: QueuerState) => TSelected) + | undefined) const queuedValue = linkedSignal(() => { return hasInitialValue diff --git a/packages/angular-pacer/tests/queuer/injectQueuedValue.spec.ts b/packages/angular-pacer/tests/queuer/injectQueuedValue.spec.ts index f2104901..13e5c607 100644 --- a/packages/angular-pacer/tests/queuer/injectQueuedValue.spec.ts +++ b/packages/angular-pacer/tests/queuer/injectQueuedValue.spec.ts @@ -76,7 +76,7 @@ describe('injectQueuedValue', () => { imports: [ChildComponent], template: '', }) - class HostComponent { } + class HostComponent {} beforeEach(async () => { await TestBed.configureTestingModule({ From a816fbcb2ba29b657d617b063b260438243f31a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Sun, 22 Feb 2026 17:57:57 -0300 Subject: [PATCH 5/6] docs: update angular generated docs --- .../reference/functions/injectQueuedValue.md | 26 ++++--- docs/framework/angular/reference/index.md | 1 + .../reference/interfaces/AsyncQueuedSignal.md | 10 +-- .../reference/interfaces/QueuedSignal.md | 10 +-- .../reference/interfaces/QueuedValueSignal.md | 78 +++++++++++++++++++ .../src/queuer/injectQueuedValue.ts | 7 +- packages/angular-pacer/tsconfig.docs.json | 5 +- 7 files changed, 110 insertions(+), 27 deletions(-) create mode 100644 docs/framework/angular/reference/interfaces/QueuedValueSignal.md diff --git a/docs/framework/angular/reference/functions/injectQueuedValue.md b/docs/framework/angular/reference/functions/injectQueuedValue.md index 9c3ed857..7e3fe4a9 100644 --- a/docs/framework/angular/reference/functions/injectQueuedValue.md +++ b/docs/framework/angular/reference/functions/injectQueuedValue.md @@ -11,10 +11,10 @@ title: injectQueuedValue function injectQueuedValue( value, options?, -selector?): QueuedSignal; +selector?): QueuedValueSignal; ``` -Defined in: [angular-pacer/src/queuer/injectQueuedValue.ts:31](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L31) +Defined in: [angular-pacer/src/queuer/injectQueuedValue.ts:38](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L38) An Angular function that creates a queued value that processes state changes in order with an optional delay. This function uses injectQueuedSignal internally to manage a queue of state changes and apply them sequentially. @@ -23,9 +23,10 @@ The queued value will process changes in the order they are received, with optio processing each change. This is useful for handling state updates that need to be processed in a specific order, like animations or sequential UI updates. -The function returns a tuple containing: -- A Signal that provides the current queued value -- The queuer instance with control methods +The function returns a callable object containing: +- `queued()`: A signal-like function that provides the current queued value +- `queued.addItem(...)`: A method to enqueue additional values +- `queued.queuer`: The queuer instance with control methods and state ### Type Parameters @@ -53,7 +54,7 @@ The function returns a tuple containing: ### Returns -[`QueuedSignal`](../interfaces/QueuedSignal.md)\<`TValue`, `TSelected`\> +[`QueuedValueSignal`](../interfaces/QueuedValueSignal.md)\<`TValue`, `TSelected`\> ### Example @@ -75,10 +76,10 @@ function injectQueuedValue( value, initialValue, options?, -selector?): QueuedSignal; +selector?): QueuedValueSignal; ``` -Defined in: [angular-pacer/src/queuer/injectQueuedValue.ts:42](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L42) +Defined in: [angular-pacer/src/queuer/injectQueuedValue.ts:49](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L49) An Angular function that creates a queued value that processes state changes in order with an optional delay. This function uses injectQueuedSignal internally to manage a queue of state changes and apply them sequentially. @@ -87,9 +88,10 @@ The queued value will process changes in the order they are received, with optio processing each change. This is useful for handling state updates that need to be processed in a specific order, like animations or sequential UI updates. -The function returns a tuple containing: -- A Signal that provides the current queued value -- The queuer instance with control methods +The function returns a callable object containing: +- `queued()`: A signal-like function that provides the current queued value +- `queued.addItem(...)`: A method to enqueue additional values +- `queued.queuer`: The queuer instance with control methods and state ### Type Parameters @@ -121,7 +123,7 @@ The function returns a tuple containing: ### Returns -[`QueuedSignal`](../interfaces/QueuedSignal.md)\<`TValue`, `TSelected`\> +[`QueuedValueSignal`](../interfaces/QueuedValueSignal.md)\<`TValue`, `TSelected`\> ### Example diff --git a/docs/framework/angular/reference/index.md b/docs/framework/angular/reference/index.md index 7dd3a08b..88d69e1c 100644 --- a/docs/framework/angular/reference/index.md +++ b/docs/framework/angular/reference/index.md @@ -30,6 +30,7 @@ title: "@tanstack/angular-pacer" - [AsyncQueuedSignal](interfaces/AsyncQueuedSignal.md) - [DebouncedSignal](interfaces/DebouncedSignal.md) - [QueuedSignal](interfaces/QueuedSignal.md) +- [QueuedValueSignal](interfaces/QueuedValueSignal.md) - [RateLimitedSignal](interfaces/RateLimitedSignal.md) - [ThrottledSignal](interfaces/ThrottledSignal.md) diff --git a/docs/framework/angular/reference/interfaces/AsyncQueuedSignal.md b/docs/framework/angular/reference/interfaces/AsyncQueuedSignal.md index 889c95ea..e6235557 100644 --- a/docs/framework/angular/reference/interfaces/AsyncQueuedSignal.md +++ b/docs/framework/angular/reference/interfaces/AsyncQueuedSignal.md @@ -33,7 +33,7 @@ Defined in: [angular-pacer/src/async-queuer/injectAsyncQueuedSignal.ts:10](https ### addItem() ```ts -addItem: (item, position?, runOnItemsChange?) => boolean; +addItem: (item, position, runOnItemsChange) => boolean; ``` Defined in: [angular-pacer/src/async-queuer/injectAsyncQueuedSignal.ts:11](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/async-queuer/injectAsyncQueuedSignal.ts#L11) @@ -47,13 +47,13 @@ Items can be inserted based on priority or at the front/back depending on config `TValue` -##### position? +##### position -`QueuePosition` +`QueuePosition` = `...` -##### runOnItemsChange? +##### runOnItemsChange -`boolean` +`boolean` = `true` #### Returns diff --git a/docs/framework/angular/reference/interfaces/QueuedSignal.md b/docs/framework/angular/reference/interfaces/QueuedSignal.md index 3e2842f5..52962fbf 100644 --- a/docs/framework/angular/reference/interfaces/QueuedSignal.md +++ b/docs/framework/angular/reference/interfaces/QueuedSignal.md @@ -33,7 +33,7 @@ Defined in: [angular-pacer/src/queuer/injectQueuedSignal.ts:7](https://github.co ### addItem() ```ts -addItem: (item, position?, runOnItemsChange?) => boolean; +addItem: (item, position, runOnItemsChange) => boolean; ``` Defined in: [angular-pacer/src/queuer/injectQueuedSignal.ts:8](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedSignal.ts#L8) @@ -55,13 +55,13 @@ queuer.addItem('task2', 'front'); `TValue` -##### position? +##### position -`QueuePosition` +`QueuePosition` = `...` -##### runOnItemsChange? +##### runOnItemsChange -`boolean` +`boolean` = `true` #### Returns diff --git a/docs/framework/angular/reference/interfaces/QueuedValueSignal.md b/docs/framework/angular/reference/interfaces/QueuedValueSignal.md new file mode 100644 index 00000000..177d0b93 --- /dev/null +++ b/docs/framework/angular/reference/interfaces/QueuedValueSignal.md @@ -0,0 +1,78 @@ +--- +id: QueuedValueSignal +title: QueuedValueSignal +--- + +# Interface: QueuedValueSignal()\ + +Defined in: [angular-pacer/src/queuer/injectQueuedValue.ts:7](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L7) + +## Type Parameters + +### TValue + +`TValue` + +### TSelected + +`TSelected` = \{ +\} + +```ts +QueuedValueSignal(): TValue; +``` + +Defined in: [angular-pacer/src/queuer/injectQueuedValue.ts:8](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L8) + +## Returns + +`TValue` + +## Properties + +### addItem() + +```ts +addItem: (item, position, runOnItemsChange) => boolean; +``` + +Defined in: [angular-pacer/src/queuer/injectQueuedValue.ts:9](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L9) + +Adds an item to the queue. If the queue is full, the item is rejected and onReject is called. +Items can be inserted based on priority or at the front/back depending on configuration. + +Returns true if the item was added, false if the queue is full. + +Example usage: +```ts +queuer.addItem('task'); +queuer.addItem('task2', 'front'); +``` + +#### Parameters + +##### item + +`TValue` + +##### position + +`QueuePosition` = `...` + +##### runOnItemsChange + +`boolean` = `true` + +#### Returns + +`boolean` + +*** + +### queuer + +```ts +queuer: AngularQueuer; +``` + +Defined in: [angular-pacer/src/queuer/injectQueuedValue.ts:10](https://github.com/TanStack/pacer/blob/main/packages/angular-pacer/src/queuer/injectQueuedValue.ts#L10) diff --git a/packages/angular-pacer/src/queuer/injectQueuedValue.ts b/packages/angular-pacer/src/queuer/injectQueuedValue.ts index e65e27e9..5601f8a2 100644 --- a/packages/angular-pacer/src/queuer/injectQueuedValue.ts +++ b/packages/angular-pacer/src/queuer/injectQueuedValue.ts @@ -18,9 +18,10 @@ export interface QueuedValueSignal { * processing each change. This is useful for handling state updates that need to be processed * in a specific order, like animations or sequential UI updates. * - * The function returns a tuple containing: - * - A Signal that provides the current queued value - * - The queuer instance with control methods + * The function returns a callable object containing: + * - `queued()`: A signal-like function that provides the current queued value + * - `queued.addItem(...)`: A method to enqueue additional values + * - `queued.queuer`: The queuer instance with control methods and state * * @example * ```ts diff --git a/packages/angular-pacer/tsconfig.docs.json b/packages/angular-pacer/tsconfig.docs.json index 20ccb038..a12189d9 100644 --- a/packages/angular-pacer/tsconfig.docs.json +++ b/packages/angular-pacer/tsconfig.docs.json @@ -1,8 +1,9 @@ { - "extends": "./tsconfig.json", + "extends": "./tsconfig.lib.json", "compilerOptions": { "paths": { - "@tanstack/pacer": ["../pacer/src"] + "@tanstack/pacer": ["../pacer/src"], + "@tanstack/pacer/*": ["../pacer/src/*"] } }, "include": ["src"] From c8d1f5f011a3a8b7bbd2194f4d43036ec3798a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Sun, 22 Feb 2026 18:09:04 -0300 Subject: [PATCH 6/6] chore: add changeset --- .changeset/big-jobs-call.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/big-jobs-call.md diff --git a/.changeset/big-jobs-call.md b/.changeset/big-jobs-call.md new file mode 100644 index 00000000..fd2613ca --- /dev/null +++ b/.changeset/big-jobs-call.md @@ -0,0 +1,5 @@ +--- +'@tanstack/angular-pacer': minor +--- + +injectQueuedValue returns queued value instead of array