# 개요

만약 실시간 검색창 또는 실시간 검증 Input이 있다고 하자.

검색어가 입력하는 것을 감지해서, 서버에 검색 request 또는 검증 request 를 보내서, 처리한 결과를 업데이트하게 된다.

그러나, 검색어 하나가 입력될 때마다 request를 호출하는 것은 굉장히 비효율적이다.

그리고, request 주기가 빠르면, 이전에 request가 다 처리되기도 전에 새로운 request의 결과로 덮어씌워야하는 경우도 발생한다.

단순히, formcontrol.valueChanges() Observable[또는 keyup event]을 단순히 subscribe함으로 처리를 한다면 한글자마다, request가 발생하게된다. [한글의 경우, '홍길동'이라고 한다면, ㅎ,ㅗ,ㅇ,ㄱ,ㅣ,ㄹ,ㄷ,ㅗ,ㅇ 입력시에 모두 호출되게 된다.]

아래와 같은 rxjs operator를 통해 비효율적인 request를 막고, 원하는 결과를 얻을 수 있다.

RxJS operator를 활용해서 얼마나 자주 특정 행동을 해야 하는지 컨트롤할 수 있다.


this.searchControl.valueChanges
      .subscribe((term=> {
        this.filterOption.keyword = term
        this.filterOption.page = 1;
        this.reloadTable();
      });
// '홍길동' 입력시 9번 Trigger
// '홍길동' 입력시 3번 Trigger (formControl에서 한글자씩 지원해줄 경우)
홍ㄱ
홍기
홍길
홍길ㄷ
홍길도
홍길동


# debounceTime(milsec) : Trigger된 Output 값 중에서, 해당 시간보다 적은 시간에 배출된 값들을 버린다.

해당 operator를 사용할 경우, 지정된 시점 이후의 값만 Trigger되게 된다.

사용자가 타이핑을 멈추고 특정 밀리세컨드가 지나야, 새로운 값이 배출되는 것이다.

value changes 이벤트가 있는 후, 특정 ms를 대기하다가, 해당 시점의 값만 Trigger 한다.

※ 특정 ms 동안의 값은 무시되기 때문에, 

해당 시간동안 기존 'AB'에서 'ABC'로 바꾸었다가, 다시 'AB'로 바꾸었을 경우, 

값이 변경되지 않았음에도 불구하고, 이전과 동일하게 'AB'를 다시 호출하게되는 단점이 있다. (동일한 request 이므로, 비효율적이다.)

 

this.searchControl.valueChanges
      .pipe(debounceTime(500), untilDestroyed(this))
      .subscribe((term=> {
        this.filterOption.keyword = term
        this.filterOption.page = 1;
        this.reloadTable();
      });
// '홍길동이 집에 내려갔습니다' 입력시
// valueChanges event 발생 이후, 500ms 이전 값은 무시, 500ms 이후의 값만 Trigger
// 아무리 valueChanges event가 많이 trigger되도, 500ms마다 한번씩만 결과가 trigger 된다.
홍길동이
홍길동이 집에 내려갔
홍길동이 집에 내려갔습니다
홍길동이 집에 내려갔습니다 //홍길동이 집에 내려갔습니당ㅇ 오타냈다가, 500ms 안에 다시 고쳤을 경우



# distinctUntilChanged() :  현재의 값마지막 입력한 값과 다를 때만 값을 내보낸다.

위에서, debounceTime() operator가 특정 시간의 output을 무시한다는 특성으로 인해, 동일한 값을 또 호출하는 경우도 존재한다는 단점을 해당 operator가 보완해줄 수 있다.

해당 operator를 사용할 경우, 현재 값을 가장 마지막에 trigger된 값과 비교해서, 다를 경우에만 내보낸다.


this.searchControl.valueChanges
      .pipe(debounceTime(500), distinctUntilChanged(), untilDestroyed(this)) 
      .subscribe((term=> {
        this.filterOption.keyword = term
        this.filterOption.page = 1;
        this.reloadTable();
      });
// '홍길동이 집에 내려갔습니다' 입력시
// valueChanges event 발생 이후, 500ms 이전 값은 무시, 500ms 이후의 값만 Trigger
// 아무리 valueChanges event가 많이 trigger되도, 500ms마다 한번씩만 결과가 trigger 된다.
홍길동이
홍길동이 집에 내려갔
홍길동이 집에 내려갔습니다
//홍길동이 집에 내려갔습니당ㅇ 오타냈다가, 500ms 안에 다시 고쳤을 경우에도 trigger되지 않음


# switchMap() : 구독 중이던 Observable이 끝나기 전에 새로운 Observable을 구독하면

이전에 구독하고 있던 Observable을 구독 취소하고 다음 Observable을 구독한다.

서버에서 받은 여러 개의 결과값들을 하나의 observable로 받을 수 있다.

Observable을 하나로 합친다고 생각해서, Merge라고 생각할 수 있으나, Merge의 개념 보다는 동일한 성격의 Observable이라면 앞의 Observable을 다 버리고, 마지막 Observable만 살린다는 의미라고 생각하면 된다. 이는 위에서 '이전에 request가 다 처리되기도 전에 새로운 request의 결과로 덮어씌워야하는 경우'에 유용하게 사용될 수 있다.

새로운 observable로 만들어 버린다. 원래는 searchControl.valueChanges에 대한 Observable()이었으나, getAll()에 대한 Observable()로 변환

서버에 요청하는 도중에 또 새로운 요청을 하면 처음에 요청 중이던 작업은 취소하고 그다음에 들어온 요청을 작업


this.searchControl.valueChanges
      .pipe(debounceTime(100), distinctUntilChanged(),switchMap(term => {
        this.filterOption.keyword = term
        this.filterOption.page = 1;
        return this.service.getAll(this.filterOption) // api request observable
// 마지막 request 제외, 자동으로 unsubscribe()해주므로,
// .pipe(untilDestroyed(this)) 생략
      }), untilDestroyed(this)).subscribe((entities=> {})
// subscribe의 trigger되는 값이 request observable의 결과값으로 완전 변경됨


# 실시간 검색 예시

filterOptionTaskFilterDto = {
    // 1. Filtering
    keyword: '',               
    start: startOfDay(subDays(new Date(), 1)).toISOString(), 
    // 2. Pagination
    limit: 10,                 // page 개당 수
    page: 1,                   // page 수
    // 3. Sorting
    orderBy: 'createdAt',      // orderBy 컬럼명
    orderDir: 'desc'           // orderBy 순서
  };

ngOnInit() {
this.searchControl.valueChanges
      .pipe(debounceTime(500), distinctUntilChanged(), switchMap(term => {
        this.filterOption.keyword = term
        this.filterOption.page = 1;
        return this.service.getAll(this.filterOption)
      }),untilDestroyed(this)).subscribe((entities=> {})
}


# 출처

https://kkangdda.tistory.com/m/84?category=830981

https://medium.com/@kavisha.talsania/rxjs-debouncetime-and-distinctuntilchanged-c9f11db4d45f

https://alligator.io/angular/real-time-search-angular-rxjs/

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기