I'm working on an Angular app and among other features, now I'm working on an image gallery (carousel).
When the user opens the image gallery, he's also able to upload new photos.
Because I need to know the upload process globally, I'm trying to implement a service for this as follow:
In ts component I have the following function:
onUploadFile(files: File[]) {
if (!files?.length) {
return;
}
const uploading = this.attachmentsService
.uploadMultiple(files)
.map((x) => ({ attachment: x }));
uploading.forEach((file) => {
this.filesStateService
.getProgress(file.attachment.url)
.pipe(untilDestroyed(this))
.subscribe({
complete: () => {
this.referenceAttachments = [...this.referenceAttachments];
},
error: () => {
this.deleteAttachment(file);
},
});
});
this.referenceAttachments = [...uploading, ...this.referenceAttachments];
}
attachments.service.ts:
uploadMultiple(files: File[]) {
if (!files?.length) {
return [];
}
return files.map((x) => this.upload(x));
}
upload(file: File) {
const fileUpload = this.filesService.upload(file);
this.filesStateService.addProgress(fileUpload.url, fileUpload.progress$);
const attachment: Attachment = {
url: fileUpload.url,
size: file.size,
type: file.type,
};
return attachment;
}
files.service.ts:
upload(file: File) {
...
return {
url: fileUrl,
progress$: new Observable<number>((observer) => {
blockBlobClient
.uploadData(file, {
onProgress: this.onProgress(observer),
})
.then(
this.onUploadComplete(observer, file, fileUrl),
this.onUploadError(observer, fileUrl)
);
}).pipe(distinctUntilChanged(), startWith(0), shareReplay()),
};
}
private onUploadError(observer: Subscriber<number>, fileUrl: string) {
return (error: any) => {
observer.error(error);
this.filesStateService.removeProgress(fileUrl);
};
}
private onUploadComplete(
observer: Subscriber<number>,
file: File,
fileUrl: string
) {
return () => {
observer.next(file.size);
this.filesStateService.removeProgress(fileUrl);
observer.complete();
};
}
private onProgress(observer: Subscriber<number>) {
return (progress: TransferProgressEvent) =>
observer.next(progress.loadedBytes);
}
In the view, the progress is rendered as follow:
<app-file-upload-progress
*ngIf="file.attachment.url | fileProgress | async as progress"
[progress]="progress"
[size]="file.attachment?.size"
></app-file-upload-progress>
file-progress.pipe is just returning the progress form the list:
@Pipe({
name: 'fileProgress',
})
export class FileProgressPipe implements PipeTransform {
constructor(private filesStateService: FilesStateService) {}
transform(value: string): Observable<number> {
if (!value) {
return null;
}
return this.filesStateService.getProgress(value);
}
}
and the files-state.service where the files progress are stored:
@Injectable()
export class FilesStateService {
filesProgress: Map<string, Observable<number>> = new Map();
addProgress(url: string, progress$: Observable<number>): void {
this.filesProgress.set(url, progress$);
}
removeProgress(url: string): void {
this.filesProgress.delete(url);
}
getProgress(url: string): Observable<number> {
return this.filesProgress.get(url);
}
}
I can't understand why, after the file was successfully uploaded and the progress is removed from the filesProgress
list, the app-file-upload-progress is not removed from the interface.
I also tried to use pure: false
in file-progress.pipe and with this, after the upload is complete if I click on another element, the changes are reflected and the progress is removed from UI (but from what I've read, using pure: false
will lead to poor performance.