2

I am trying to implement a buffer inside an angular service to prevent an http call to go out multiple times should it get called multiple times in rapid succession. To achieve this, I'm using the debounceTime operator and a BehaviourSubject to instantly emit its last value on every new subscription.

@Injectable({
  providedIn: 'root'
})
class MyService {
  private requestBuffer = new BehaviorSubject<any>(null);
  private requestBuffer$ = this.requestBuffer
    .asObservable()
    .pipe(
      debounceTime(1000),
      mergeMap(() => this.http.get('/myurl'))
    );

  constructor(private http: HttpClient) { }

  doRequest() {
    return this.requestBuffer$;
  }
}

@Component({
  selector: 'app-my-component-1'
})
class MyComponent1 implements OnInit {
  constructor(private myService: MyService) { }
  
  ngOnInit() {
    this.myService.doRequest()
      .subscribe()
  }
}

@Component({
  selector: 'app-my-component-2'
})
class MyComponent2 implements OnInit {
  constructor(private myService: MyService) { }
  
  ngOnInit() {
    this.myService.doRequest()
      .subscribe()
  }
}

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: [
    MyComponent1,
    MyComponent2
  ],
  providers: [
    MyService,
  ]
})
class MyModule {

  constructor() { }
}
<app-my-component-1 />
<app-my-component-2 />

The above code is not working and I'm not really understanding what am I doing wrong. As both components reach their OnInit lifecycle hook, I expected that HttpClient's get method would be debounced and only called once. However, this is not the case, two get calls are being fired.

I hope that the explanation is clear enough.

To my end goal of obtaining a buffered http call, what is it that I'm doing wrong / misunderstanding?

Thank you!

2
  • Try to use switchMap maybe? Commented Aug 8, 2018 at 12:50
  • Tried it, same result. I too thought that this would solve the issue, as switchMap discards previously emitted values in favour of the last one... not the case. Commented Aug 8, 2018 at 13:11

1 Answer 1

4

You have to add the share() operator to share the observable between multiple subscriptions:

private requestBuffer$ = this.requestBuffer
 .asObservable()
 .pipe(
   debounceTime(1000),
   mergeMap(() => this.http.get('/myurl')),
   share()
);

You don't need to use a BehaviourSubject if you use shareReplay(), but in the end, it doesn't really matter.

To 'ditch' the BehaviourSubject, I suspect this should be enough:

class MyService {
  private requestBuffer$ = this.http.get('/myurl').pipe(
    debounceTime(1000),
    shareReplay()
  );

  constructor(private http: HttpClient) { }

  doRequest() {
    return this.requestBuffer$;
  }
}

Although I'm afraid if you do two calls, and they are more than 2 seconds apart, the 2nd call does not fire, but you should test that out yourself

Sign up to request clarification or add additional context in comments.

2 Comments

This works, thank you! Can you please provide some detail or a link on how exactly can I ditch the BehaviourSubject?
Makes complete sense and is way more elegant. Thank you for shedding light on matter, you saved me from some ignorant over engineering!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.