3

im trying to add a progressbar but sometimes it bugs out and fills too slow or too fast, most of the time it works fine though.

I also think that there must be a better way to programm this since mine does way to many calculations for a single progressbar i think.

Here is my Code:

ts:

startProgressbar(timeToWait: number) {
this.progress = 0;
const interval = setInterval(() => {
  let addvalue = (1 / timeToWait * 100) / 64;
  this.progress += addvalue;
  if (this.progress >= 100) {
    this.progress = 100;
    window.clearInterval(interval);
  }
}, 15.625); }

The function gets called again as soon as its over since there will be the next slide of the slideshow directly after.

The progress value resembles the width of the progressbar, its always a percentage.

Where does the number 15.625 come from? Its simply a 64th of a second so the progressbar updates 64 times a second giving the illusion of moving smoothly.

My html is pretty basic:

<div class="progressbar" [ngStyle]="{'width': progress+'%'}"></div>

I hope you have a better approach than i did. Thanks in advance

[EDIT]: What i mean by it bugging out is that the progressbar sometimes starts filling late and therefor doesnt reach the end (100%) before the next slide appears and then the next ones timing is offset too. Sometimes it starts early and then reaches the end (100%) too quickly so it stays at 100% for a while until the next slide appears.

niehoelzz
  • 307
  • 1
  • 15

2 Answers2

4

why not use Rxjs timer?

interval(timeToWait):Observable<number>
  {
    let progress = 0;
    return timer(0, timeToWait)
      .pipe(
        takeWhile(() => progress <= 100),
        map(()=>progress<100?progress:100),
        tap(() => (progress += 10))
      )
  }

Use

   this.interval(200).subscribe((res) => {
        console.log(res);
      });

Update add stackblitz: stackblitz

Update 2 thanks to @Ritaj, timer return a stream (0,1,2,3,4,...), so we can use simple

 return timer(0, timeToWait)
  .pipe(
    map((progress)=>progress*5<100?progress*5:100),
    takeWhile((progress) => progress <= 100)
  )

Update 3 thanks to this answer of SO, we can included the last value whe we are using takeWhile, so, if is important the timeToWait we can replace the function interval by

 interval(timeToWait) {
    const initial = new Date().getTime();
    return timer(0, 200).pipe(
      map(()=> new Date().getTime()),
      takeWhile((res) => res<=initial+timeToWait,true),
      map(now=>{
        const porc = (100 * (now - initial)) / timeToWait;
        return porc<100?Math.round(porc):100
      })
    );
Eliseo
  • 29,422
  • 4
  • 13
  • 37
  • Because i've never used it, this was the first time i had to do with timing in angular, im only doing my apprenticeship since 5 months but i will make sure to look into that ^^ – niehoelzz Jan 09 '20 at 11:17
  • Also when i copy your example it doesn't work; Cant find name 'takeWhile', 'map', 'tap'. I imported: import { Observable } from 'rxjs/internal/Observable'; import { timer } from 'rxjs/internal/observable/timer'; – niehoelzz Jan 09 '20 at 11:43
  • you must import from 'rxjs/operators, I updated the answer with a stackblitz: NOTE, from Angular 6 -I think remember-, Rxjs change to RXJS 6, so you import from 'rxjs' and 'rxjs/operators' – Eliseo Jan 09 '20 at 11:58
  • 1
    This is a better answer, but you should `map` before `takeWhile`. Also there is no neet to have a `progress` variable, just `map` and return the stream. – Roberto Zvjerković Jan 09 '20 at 12:05
  • 1
    @niehoelzz, just updated the stackblitz and the answer with the comment of Ritaj – Eliseo Jan 09 '20 at 12:14
  • when i adjust the stepsize and the timeToWait, the progressbar doesn't reach 100% in time, why could that be? I changed progress to += 0.1 instead of 1 and timeToWait to * 0.1 aswell. On paper maths that would add up – niehoelzz Jan 09 '20 at 13:03
  • I update the answer with another version of interval that use new Date – Eliseo Jan 10 '20 at 07:49
1

I recommend following the Javascript best practice for waiting. Here is a stackblitz I've prepared with example code for a progress bar that uses the recommended practices.

export class AppComponent  {
  name = 'Angular';
  public progress = 0;
  public timeToWait: number = 1;

  private default_ms = 8.33; // 120 times a second.
  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  public async startProgressBar(): Promise<void> {
    this.progress = 0;
    while(this.progress <= 100) {
      this.progress += (1 / this.timeToWait);
      await this.sleep(this.default_ms);
    }
  }
}

I can't say for sure why your solution "bugs out". Calling window.clearInterval inside the interval function seems like bad practice, but I can't be certain. Let me know if this solution works for you.

igg
  • 1,832
  • 2
  • 8
  • 27