Search code examples
angularsignalrrxjs

How to use debounceTime but still trigger the function after certain time?


TLDR; I would like to use debounceTime to execute the function only if 300 milliseconds have passed without it being called. In the meanwhile, I also want to be able to trigger the function every 1 minutes If the process takes a long time. Otherwise, the function will only be triggered at the end of the process.


Basically, our system has a long process that will fire a lot of SignalR update to the client. When I received the server command on the client, I will make 2 additional HTTP requests back to server to get some information. So that It will hit the server back as long as the server sending me update.

I am using debounceTime to prevent sending too many requests back to the server If the time between two commands is within 300ms. But there is one use case where the server constantly sending the update to the client in, e.g 1 hour. Meaning the client will trigger the getItemCount at 1h and 300ms.

export class LeftNavigationComponent implements OnInit, OnDestroy {
    typeACount: number = 0;
    typeBCount: number = 0;    

    constructor(
        private itemService: service.ItemService,        
        private signalR: service.SignalRService) { }

    ngOnInit(): void {
        this.subscriptions = [            
            this.signalR.itemCreated.debounceTime(300).subscribe(item => this.onUpdatingData())]
    }

    onUpdatingData() {
        Promise.all([
            this.itemService.getItemCount(APP_SETTING.TYPE_A),
            this.itemService.getItemCount(APP_SETTING.TYPE_B)])
            .then(response => this.gettingCountDone(response))
    }

    gettingCountDone(response) {
        this.typeACount = <any>response[0];
        this.typeBCount = <any>response[1];        
    }
}

I still want to debounceTime to prevent sending too many requests to the server. But it should be smart enough to be automatically triggered every e.g 1 minutes after receiving the first updated. Has anyone had the use case before?


Solution

  • Pavel answer is close, but If I have understood well the question, you want this:

    ngOnInit(): void {
        const debounced = this.signalR.itemCreated.debounceTime(300).share();
        this.subscriptions = [         
            debounced.subscribe(() => this.onUpdatingData()),
            debounced.switchMap(() => Observable.interval(60000).takeUntil(this.signalR.itemCreated)).subscribe(() => this.onUpdatingData())
        ]
    }
    

    This code will do the following, when the time between items created are major than 300ms onUpdatingData() will be called. After that Every time debounced emits a value, a throttleTime observable of 1 minit is created. Meaning that, if debounced doesn't emit for a minut since the last emision, onUpdatingData() will be executed, and so one.

    And improvement would be to merge the observables, because they are from the same type and the execute the same function, for example like this:

    ngOnInit(): void {
        const debounced = this.signalR.itemCreated.debounceTime(300).share();
        const merged = debounced.switchMap(() => Observable.interval(60000).takeUntil(this.signalR.itemCreated))
        this.subscriptions = [         
          merged.subscribe(() => this.onUpdatingData())
       ]
    }
    

    I posted a fiddle showing the working solution. In this fiddle, the event mousedown simulates the stream this.signalR.itemCreated.

    https://jsfiddle.net/llpujol/e6b6o655/