Detecting change of a BehaviorSubject component member variable updated in a subscribe block?

匆匆过客 提交于 2021-01-16 04:32:33

问题


I have an element in a component view:

<div>Progress: {{ progress$ | async }}</div>

It is supposed to be updated in a continuous way by a next() call in a subscribe() block:

progress$: BehaviorSubject<number> = new BehaviorSubject(0);
downloadSoundtrack(soundtrack: Soundtrack): void {
  const fileName: string = soundtrack.name + '.' + MIDI_FILE_SUFFIX;
  const progress$: Observable<ProgressTask<Uint8Array>> = this.midiService.progressiveCreateSoundtrackMidi$(soundtrack);
  console.log('Created the observable');
  const piper$: Observable<ProgressTask<Uint8Array>> = progress$.pipe(
    tap((progressTask: ProgressTask<Uint8Array>) => {
      this.progress$.next(progressTask.loaded);
      console.log('Loaded: ' + progressTask.loaded);
    })
  );
  this.download$ = this.downloadService.downloadObservableDataAsBlobWithProgressAndSaveInFile(piper$, fileName);
  console.log('Controller method call complete');
}

When running it, the console log shows:

Created the observable
Controller method call complete
Loaded: 0
Loaded: 1
Loaded: 2
...
Loaded: 591

However, the element in the view is not updating continuously, but only at the last value, changing from 0 to 591 straight.

I tried adding a this.detectChanges(); after the next() call but it didn't help.

I tried wrapping the next() call within an ngZone block but it didn't help.

The sleep method is implemented as:

public sleep(milliseconds: number): void {
  const date = Date.now();
  let currentDate = null;
  do {
    currentDate = Date.now();
  } while (currentDate - date < milliseconds);
}

On Angular 11.

UPDATE: I created another component method to trigger an interval() observable and that worked. It did show the progress value in the template changing continuously.

testProgress(): void {
  console.log('Called the testProgress method');
  interval(1000)
  .subscribe((value: number) => {
    this.progressSubject$.next(value);
    this.detectChanges();
    console.log('The progress: ' + value);
  });
}

UPDATE: I also tried subscribing to the first observable as in:

downloadSoundtrack(soundtrack: Soundtrack): void {
  const fileName: string = soundtrack.name + '.' + MIDI_FILE_SUFFIX;
  const progress$: Observable<ProgressTask<Uint8Array>> = this.midiService.progressiveCreateSoundtrackMidi$(soundtrack);
  console.log('Created the observable');
  const piper$: Observable<ProgressTask<Uint8Array>> = progress$
  .subscribe((progressTask: ProgressTask<Uint8Array>) => {
    this.progressSubject$.next(progressTask.loaded);
    this.detectChanges();
    console.log('Loaded: ' + progressTask.loaded);
  });
  console.log('Controller method call complete');
}

But the progress in the template still didn't update before the last value.

UPDATE: This time I tried to change the way the progress is being produced by the service class. Instead of doing it with a download as in:

public progressiveCreateSoundtrackMidi$(soundtrack: Soundtrack): Observable<ProgressTask<Uint8Array>> {
   return new Observable((observer$ : Subscriber<ProgressTask<Uint8Array>>) => {
     this.createSoundtrackMidi(soundtrack, observer$);
     return { unsubscribe() { } };
   });
}

I faked it with:

public progressiveCreateSoundtrackMidi$(soundtrack: Soundtrack): Observable<ProgressTask<Uint8Array>> {
  return interval(1000)
  .pipe(
    map((value: number) => {
      return this.downloadService.createProgressTask<Uint8Array>(1000, value);
    })
  );
}

And this now works fine with the progress being changed and reflected in the template continuously.

There is something fishy in my createSoundtrackMidi service method:

public createSoundtrackMidi(soundtrack: Soundtrack, progressTask$?: Subscriber<ProgressTask<Uint8Array>>): Uint8Array;

So I faked this above service method with:

public createSoundtrackMidi(soundtrack: Soundtrack, progressTask$?: Subscriber<ProgressTask<Uint8Array>>): Uint8Array {
  const midi: Midi = new Midi();
  if (progressTask$){
    for (let index: number = 0; index < 1000; index++) {
      this.commonService.sleep(10);
      progressTask$.next(this.downloadService.createProgressTask<Uint8Array>(1000, index));
    }
    progressTask$.next(this.downloadService.createProgressTask<Uint8Array>(1000, 1000));
    progressTask$.complete();
  }
  return midi.toArray();
}

and the issue showed up again.

To see if the method parameter progressTask$?: Subscriber<ProgressTask<Uint8Array>> was updating only a local copy I faked the following service method:

public progressiveCreateSoundtrackMidi$(soundtrack: Soundtrack): Observable<ProgressTask<Uint8Array>> {
  return new Observable((observer$ : Subscriber<ProgressTask<Uint8Array>>) => {
    for (let index: number = 0; index < 1000; index++) {
      this.commonService.sleep(10);
      observer$.next(this.downloadService.createProgressTask<Uint8Array>(1000, index));
    }
    observer$.next(this.downloadService.createProgressTask<Uint8Array>(1000, 1000));
    observer$.complete();
    return { unsubscribe() { } };
  });
}

and the issue showed up. The method call is not the cause for the issue.

The issue does not show either when using my own interval implementation:

public progressiveCreateSoundtrackMidi$(soundtrack: Soundtrack): Observable<ProgressTask<Uint8Array>> {
  const max: number = 200;
  return this.interval(max)
  .pipe(
    map((value: number) => {
      return this.downloadService.createProgressTask<Uint8Array>(max, value);
    })
  );
}

private interval(period: number): Observable<number> {
  return new Observable((observer$: Subscriber<number>) => {
    let i = 0;
    const handler = setInterval(() => observer$.next(i++), period);
    return () => clearInterval(handler);
  });
}

Is it because the setInterval() function is un-blocking between each iteration ?

The issue does not show either with:

public progressiveCreateSoundtrackMidi$(soundtrack: Soundtrack): Observable<ProgressTask<Uint8Array>> {
  const max: number = 200;

  return new Observable((observer$: Subscriber<ProgressTask<Uint8Array>>) => {
    let index = 0;
    const handler = setInterval(() => {
      observer$.next(this.downloadService.createProgressTask<Uint8Array>(max, index));
      index++;
    }, max);
    return () => clearInterval(handler);
  });
}

But a loop in a setTimeout() function call shows the issue:

public progressiveCreateSoundtrackMidi$(soundtrack: Soundtrack): Observable<ProgressTask<Uint8Array>> {
  const max: number = 200;

  return new Observable((observer$: Subscriber<ProgressTask<Uint8Array>>) => {
    const handler = setTimeout(() => {
      for (let index: number = 0; index < max; index++) {
        this.commonService.sleep(10);
        observer$.next(this.downloadService.createProgressTask<Uint8Array>(max, index));
      }
      observer$.next(this.downloadService.createProgressTask<Uint8Array>(max, max));
      observer$.complete();
    }, max);
    return () => clearTimeout(handler);
  });
}

How to un-block between each loop iteration, in the same fashion the setInterval() function is doing ?

来源:https://stackoverflow.com/questions/65060800/detecting-change-of-a-behaviorsubject-component-member-variable-updated-in-a-sub

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!