본문으로 건너뛰기

TanStack Pacer Angular 어댑터

Angular 애플리케이션에서 TanStack Pacer를 사용한다면 Angular 어댑터 사용을 권장합니다. Angular 어댑터는 핵심 Pacer 유틸리티를 래핑하고 Angular의 의존성 주입 및 시그널과 통합하는 inject 함수를 제공합니다. 핵심 Pacer 클래스나 함수를 직접 사용해야 하는 경우를 위해 Angular 어댑터는 핵심 패키지의 모든 항목도 다시 내보냅니다.

설치

npm install @tanstack/angular-pacer

Angular inject API

inject 함수 전체 목록(injectDebouncer, injectThrottler, injectRateLimiter, injectQueuer, injectBatcher와 각각의 비동기 및 콜백 변형)은 Angular inject API 레퍼런스를 참고합니다.

기본 사용법

컴포넌트나 서비스에 Pacer 유틸리티를 주입합니다. 각 inject 함수에 셀렉터를 전달하면 메서드와 반응형 state() 시그널을 노출하는 객체를 반환합니다.

import { Component, signal } from '@angular/core'
import { injectDebouncer } from '@tanstack/angular-pacer'

@Component({
selector: 'app-root',
template: `
<input [value]="query()" (input)="onInput($event)" placeholder="Search..." />
<p>Pending: {{ debouncer.state().isPending }}</p>
<p>Debounced: {{ debounced() }}</p>
`,
})
export class App {
protected readonly query = signal('')
protected readonly debounced = signal('')

protected readonly debouncer = injectDebouncer(
(q: string) => {
this.debounced.set(q)
},
{ wait: 500 },
(state) => ({ isPending: state.isPending }),
)

protected onInput(event: Event): void {
const value = (event.target as HTMLInputElement).value
this.query.set(value)
this.debouncer.maybeExecute(value)
}
}

어댑터가 다시 내보내는 핵심 Pacer API도 가져올 수 있습니다.

import { debounce, Debouncer } from '@tanstack/angular-pacer'

프로바이더

애플리케이션 설정에서 providePacerOptions를 사용해 앱의 모든 Pacer 유틸리티에 기본 옵션을 설정합니다. 개별 inject 함수에 전달한 옵션은 이 기본값보다 우선합니다.

import { ApplicationConfig } from '@angular/core'
import { providePacerOptions } from '@tanstack/angular-pacer'

export const appConfig: ApplicationConfig = {
providers: [
providePacerOptions({
debouncer: { wait: 300 },
throttler: { wait: 100 },
asyncQueuer: { concurrency: 2 },
rateLimiter: { limit: 5, window: 60000 },
}),
],
}

상태 셀렉터

각 inject 함수의 세 번째 인수는 상태 셀렉터입니다. 반환된 객체의 state() 시그널에 노출할 상태 조각을 결정하므로 관련 변경만 템플릿 업데이트를 트리거합니다.

기본적으로 셀렉터를 생략하면 state()가 채워지지 않습니다. 반응형 상태를 사용하려면 셀렉터를 전달합니다.

// No selector: state() is not populated
const debouncer = injectDebouncer(fn, { wait: 500 })

// With selector: state() is a signal of the selected slice
const debouncer = injectDebouncer(
fn,
{ wait: 500 },
(state) => ({ isPending: state.isPending }),
)

유틸리티별 상태와 옵션에 관한 자세한 내용은 가이드(예: 디바운스 가이드, 요청률 제한 가이드)를 참고합니다.

예시

디바운서

import { Component, signal } from '@angular/core'
import { injectDebouncer } from '@tanstack/angular-pacer'

@Component({
selector: 'app-search',
template: `
<input [value]="query()" (input)="onInput($event)" placeholder="Search..." />
`,
})
export class SearchComponent {
protected readonly query = signal('')

protected readonly debouncer = injectDebouncer(
(q: string) => console.log('Searching for', q),
{ wait: 500 },
)

protected onInput(event: Event): void {
const value = (event.target as HTMLInputElement).value
this.query.set(value)
this.debouncer.maybeExecute(value)
}
}

비동기 큐어

import { Component, signal } from '@angular/core'
import { injectAsyncQueuer } from '@tanstack/angular-pacer'

@Component({
selector: 'app-upload',
template: `
<input type="file" multiple (change)="onFiles($event)" />
<p>Queue size: {{ queuer.state().size }}</p>
`,
})
export class UploadComponent {
protected readonly queuer = injectAsyncQueuer<File, { size: number }>(
async (file) => {
await uploadFile(file)
},
{ concurrency: 3 },
(state) => ({ size: state.size }),
)

protected onFiles(event: Event): void {
const files = (event.target as HTMLInputElement).files
if (files) {
Array.from(files).forEach((file) => this.queuer.addItem(file))
}
}
}

요청률 제한기

import { Component } from '@angular/core'
import { injectRateLimiter } from '@tanstack/angular-pacer'

@Component({
selector: 'app-api',
template: `
<button (click)="submit()">Submit</button>
<p>Rejections: {{ rateLimiter.state().rejectionCount }}</p>
`,
})
export class ApiComponent {
protected readonly rateLimiter = injectRateLimiter<string, { rejectionCount: number }>(
(data) =>
fetch('/api/endpoint', {
method: 'POST',
body: JSON.stringify({ data }),
}),
{
limit: 5,
window: 60000,
onReject: () => alert('Rate limit reached. Try again later.'),
},
(state) => ({ rejectionCount: state.rejectionCount }),
)

protected submit(): void {
this.rateLimiter.maybeExecute('payload')
}
}