본문 바로가기

개발 공부/Angular

Signals vs RxJS

Angular 16 이후 Signals가 도입되면서, 기존에 RxJS Observable을 쓰던 패턴을 대체할 수 있는가에 대한 논의가 많습니다.

하지만 실제로는 두 기술이 대체재라기보다는 보완재에 가깝습니다. 

Signals

  • 주요 특징
    • 단순한 state 저장소
    • 변경 시 자동으로 UI 반영
    • 동기적으로 동작 (값을 즉시 읽을 수 있음)
// counter.signal.ts
import { signal } from '@angular/core';

export const counter = signal(0);

export function increment() {
  counter.update(c => c + 1);
}
  • 장점
    • 러닝 커브가 거의 없음 (변수처럼 쓰면 됨)
    • async pipe 불필요 → 템플릿에서 바로 값 사용 가능
    • 작은 규모의 상태 관리에 매우 적합

 


RxJS

  • 주요 특징
    • 비동기 스트림을 다루는 라이브러리
    • 복잡한 이벤트 흐름, 네트워크 호출, 멀티 캐스팅 등에 강력
// user.service.ts
private user$ = new BehaviorSubject<User | null>(null);

fetchUser(id: number) {
  this.http.get<User>(`/api/users/${id}`).subscribe(user => {
    this.user$.next(user);
  });
}

get userChanges$() {
  return this.user$.asObservable();
}
  • 장점
    • 다양한 오퍼레이터 (map, switchMap, merge, …)로 복잡한 데이터 흐름 제어 가능
    • 멀티 이벤트/스트림 조합에 최적화

 


Signals vs RxJS 예시 비교

(1) 단순 상태 관리 → Signals가 적합

// dark-mode.service.ts
@Injectable({ providedIn: 'root' })
export class DarkModeService {
  darkMode = signal(false);

  toggle() {
    this.darkMode.update(v => !v);
  }
}
  • UI 버튼 클릭 시 바로 UI 반영
  • 특별한 비동기 처리 필요 없음 → RxJS는 오히려 오버스펙

 


 

(2) API 호출 및 비동기 스트림 → RxJS가 유리

// search.service.ts
@Injectable({ providedIn: 'root' })
export class SearchService {
  private query$ = new Subject<string>();

  results$ = this.query$.pipe(
    debounceTime(300),
    distinctUntilChanged(),
    switchMap(q => this.http.get(`/api/search?q=${q}`))
  );

  search(q: string) {
    this.query$.next(q);
  }
}
  • 연속 입력(검색어 자동완성) 같은 이벤트 스트림 제어에는 RxJS가 강력
  • Signals로 구현하기엔 복잡성이 커짐

 


 

(3) Signals + RxJS 조합

실무에서는 두 기술을 함께 쓰는 경우가 많습니다.

 

RxJS로 비동기 데이터 처리 → Signals로 상태 저장/반영

@Injectable({ providedIn: 'root' })
export class UserStore {
  user = signal<User | null>(null);
  loading = signal(false);

  constructor(private http: HttpClient) {}

  loadUser(id: number) {
    this.loading.set(true);

    this.http.get<User>(`/api/users/${id}`).subscribe({
      next: u => this.user.set(u),
      complete: () => this.loading.set(false)
    });
  }
}

 

또는 RxJS Observable을 Signal로 변환:

const userSignal = toSignal(this.http.get<User>('/api/me'), { initialValue: null });

 

정리

 

상황 Signals RxJS
단순한 UI 상태 (토글, 카운터, 폼 입력값 등) O X
복잡한 비동기 처리 (검색, WebSocket, 멀티 이벤트) X O
API 호출 결과를 UI에 반영 O (RxJS + Signal 조합) O
상태를 전역으로 공유 O O (둘 다 가능, 규모에 따라 선택)

 

  • Signals: 로컬 상태 관리UI 반영 최적화
  • RxJS: 비동기 스트림 처리복잡한 흐름 제어
  • 결국 둘 다 필요한 기술이며, 조합해서 쓰는 게 실무에서는 가장 현실적입니다.