Search code examples
angulartimerrxjsangular11

In Angular 11, how do you set a timer with RXJS (e.g. avoid "TypeError: You provided 'undefined' where a stream was expected" errors)


I just recently upgraded to Angular 11.2.9 and RXJS 6.6.7 . I have this in my component ...

  ngOnInit(): void {
    window.addEventListener('blur', this.onBlur, false);
    ...
  }
  ...



  onBlur(): void {
    this.trendingSet$ = timer(1, HotComponent.REFRESH_INTERVAL_IN_MS).pipe(
      switchMap(() => this.apiService.getTrending()),
      retry(),
      share(),
      takeUntil(this.stopPolling)
    );
    console.log(this.trendingSet$);
    this.trendingSet$.subscribe((result: TrendingSet) => {
      this.articleStats = result.trending_articles;
    });
  }

However, I'm getting this error on the

this.trendingSet$.subscribe((result: TrendingSet) => {

line:

ERROR TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
    RxJS 4
    onBlur hot.component.ts:90
    Angular 14
    ngOnInit hot.component.ts:35
    Angular 21
    RxJS 5
    Angular 8
        emit
        checkStable
        onHasTask
        hasTask
        _updateTaskCount
        _updateTaskCount
        runTask
        drainMicroTaskQueue
core.js:6210
    Angular 3
    RxJS 5
    Angular 20
    RxJS 10
    onBlur hot.component.ts:90
    Angular 14
    ngOnInit hot.component.ts:35
    Angular 21
    RxJS 5
    Angular 8

What's the proper way to set a timer in my component to regular query a service method?


Solution

  • Dont use window.addEventListener('blur', this.onBlur, false) when you have rxjs wrapped fromEvent...

    The initial problem here is that when you call onBlur as a callback to 'blur' event, pointer this (in this.trendingSet$) will point to Window object and not your component, in that case you have to use window.addEventListener('blur', this.onBlur.bind(this), false)

    Also, share operator is redundant here unless you are using stream this.trendingSet$ on other places as well

    EDIT

    private startPolling$ = new Subject();
    private stopPolling$ = new Subject();
    
    // Using this logic, subscription to blur event will be automatically unsubscribed once Component is being destroyed
    @HostListener('window:blur', ['$event'])
    onBlurEvent(): void {
        this.startPolling$.next();
    }
    
    ngOnInit() {
    
        const poll$ = timer(1, HotComponent.REFRESH_INTERVAL_IN_MS)
            .pipe(switchMap(() => this.apiService.getTrending()), retry());
    
        this.startPolling$.pipe(
            switchMap(() => poll$),
            takeUntil(this.stopPolling$)
        ).subscribe((response) => {
            // Doing stuff with response
        });
    }
    

    Or instead of using @HostListener you can use

        const poll$ = timer(1, HotComponent.REFRESH_INTERVAL_IN_MS)
            .pipe(switchMap(() => this.apiService.getTrending()), retry(), 
            takeUntil(this.stopPolling$));
    
        fromEvent(document, 'blur').pipe(
           switchMap(() => poll$),
       ).subscribe((response) => {
          // Doing stuff with response
       });