简体   繁体   中英

Rxjs using first() with timer()

i'm trying to call a service every 10seconds but also want to execute some code only on the first emission, the problem here is that the first entry is duplicated, here is the code:

ionViewWillEnter() {
  this.isLoading = true;
  const Obs = timer(0, 10000).pipe(
    switchMap(() => {
      return this.serviceName.someFunc();
    })
  );
  this.timerSub = Obs.subscribe();
  this.timerSub = Obs.pipe(first()).subscribe(() => {
    this.isLoading = false;
  });
}

i also noticed another problem which is that even though i unsubscribe when i leave the page, the service is still being called every 10 seconds, any help is appreciated.

UPDATE

i found a solution, but it's more of a workaround, basically what i did was put a setTimeout on the subscription:

this.timerSub = Obs.pipe(first()).subscribe(() => {
  this.isLoading = false;
});
setTimeout(() => {
  this.timerSub = Obs.subscribe();
}, 10000);

and apparently the unsubscribe problem was solved as well, although i would appreciate the feedback with some more elegant solutions, thanks in advance.

better solution:

create variable firstSub at the the top.

ionViewWillEnter() {
  this.isLoading = true;
  this.timerSub = timer(0, 10000).pipe(
    switchMap(() => {
      return this.serviceName.someFunc();
    })
  );
  this.firstSub = this.timerSub.pipe(first());
  this.firstSub.subscribe(() => {
    // only emit once(first)
    this.isLoading = false;
  });
}

unsubscribing before component view destroyed.

ngOnDestroy(){
  this.timerSub.unsubscribe();
  this.firstSub.unsubscribe();
}

The answer provided by Nilesh Patel should work fine, but I still wanted to add this answer to share some minor tips and improvements that you may need to use in your app.

Please take a look at this Stackblitz demo .

The first thing to notice is that if you're using the timer operator and you're interested in doing something the first time it emits, you can check the value returned by that operator and see if it's 0 :

timer(0, 10000).pipe(
  tap(currentTimer => {
    if (currentTimer === 0) {
      this.someFunctionToRunOnlyOnce();
    }
  }),
  // ...
);

The second thing to keep in mind is that instead of storing every subscription in a variable (to then unsubscribe from all of them) you can create a subject and use the takeUntil operator like this:

private unsubscribe$: Subject<void> = new Subject<void>();

// ...

timer(0, 10000).pipe(
  // ... other operators
  takeUntil(this.unsubscribe$) // <-- like this
).subscribe();

// ...

ngOnDestroy() {
  this.unsubscribe$.next(); // <-- this will clean the streams
  this.unsubscribe$.unsubscribe(); // <-- this will clean the unsubscribe$ stream
}

And another very minor thing to keep in mind is that you can "pause" and "resume" the stream whenever you want without "destroying" it. For example, you can pause it when leaving the page and then resume it again when the user is about to enter to the page again by using the filter operator:

private isInPage: boolean = true;

// ...

timer(0, 10000).pipe(
  filter(() => this.isInPage),
  // other operators ...
);

// ...

ionViewWillEnter() {
  this.isInPage = true;
}

ionViewWillLeave() {
  this.isInPage = false;
}

So putting all that together it'd be something like this:

import { Component, OnInit } from "@angular/core";
import { NavController } from "@ionic/angular";
import { Observable, of, Subject, timer } from "rxjs";
import { delay, filter, switchMap, takeUntil, tap } from "rxjs/operators";

@Component({
  selector: "app-home",
  templateUrl: "./home.page.html",
  styleUrls: ["./home.page.scss"]
})
export class HomePage implements OnInit {
  private isInPage: boolean = true;
  private unsubscribe$: Subject<void> = new Subject<void>();

  constructor(private navCtrl: NavController) {}

  ngOnInit() {
    timer(0, 10000)
      .pipe(
        filter(() => this.isInPage),
        tap(currentTimer => {
          if (currentTimer === 0) {
            this.someFunctionToRunOnlyOnce();
          }
        }),
        switchMap(() => {
          return this.someAsynFunction();
        }),
        takeUntil(this.unsubscribe$)
      )
      .subscribe();
  }

  ionViewWillEnter() {
    this.isInPage = true;
  }

  ionViewWillLeave() {
    this.isInPage = false;
  }

  ngOnDestroy() {
    this.unsubscribe$.next();
    this.unsubscribe$.unsubscribe();
  }

  public openDetailsPage(): void {
    this.navCtrl.navigateForward("details");
  }

  private someAsynFunction(): Observable<number> {
    const randomNumber = Math.floor(Math.random() * 10000) + 1;

    console.log("==> Running someAsynFunction method");
    return of(randomNumber).pipe(delay(1000));
  }

  private someFunctionToRunOnlyOnce(): void {
    console.log("==> Running someAsynFunctionToRunOnlyOnce method");
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM