diff --git a/packages/forms/signals/src/api/rules/debounce.ts b/packages/forms/signals/src/api/rules/debounce.ts index 1503e6483d5..a050f323259 100644 --- a/packages/forms/signals/src/api/rules/debounce.ts +++ b/packages/forms/signals/src/api/rules/debounce.ts @@ -42,8 +42,18 @@ export function debounce( function debounceForDuration(durationInMilliseconds: number): Debouncer { return (_context, abortSignal) => { return new Promise((resolve) => { - const timeoutId = setTimeout(resolve, durationInMilliseconds); - abortSignal.addEventListener('abort', () => clearTimeout(timeoutId)); + let timeoutId: ReturnType | undefined; + + const onAbort = () => { + clearTimeout(timeoutId); + }; + + timeoutId = setTimeout(() => { + abortSignal.removeEventListener('abort', onAbort); + resolve(); + }, durationInMilliseconds); + + abortSignal.addEventListener('abort', onAbort, {once: true}); }); }; } diff --git a/packages/forms/signals/test/node/api/debounce.spec.ts b/packages/forms/signals/test/node/api/debounce.spec.ts index dbd43829348..f7c18d78132 100644 --- a/packages/forms/signals/test/node/api/debounce.spec.ts +++ b/packages/forms/signals/test/node/api/debounce.spec.ts @@ -237,6 +237,34 @@ describe('debounce', () => { expect(abortSpy).toHaveBeenCalledTimes(1); expect(street.value()).toBe('1600 Amphitheatre Pkwy'); }); + + it('should remove abort listener when debounce completes', async () => { + const addListenerSpy = spyOn(AbortSignal.prototype, 'addEventListener').and.callThrough(); + const removeListenerSpy = spyOn( + AbortSignal.prototype, + 'removeEventListener', + ).and.callThrough(); + + const address = signal({street: ''}); + const addressForm = form( + address, + (address) => { + debounce(address.street, 1); + }, + options(), + ); + const street = addressForm.street(); + + street.setControlValue('1600 Amphitheatre Pkwy'); + expect(addListenerSpy).toHaveBeenCalledOnceWith('abort', jasmine.any(Function), { + once: true, + }); + expect(removeListenerSpy).not.toHaveBeenCalled(); + + await timeout(10); + expect(street.value()).toBe('1600 Amphitheatre Pkwy'); + expect(removeListenerSpy).toHaveBeenCalledOnceWith('abort', jasmine.any(Function)); + }); }); });