问题
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