1

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.

Sergiu Molnar
  • 865
  • 1
  • 11
  • 22

2 Answers2

0

You need to use a ChangeDetectorRef service.

ChangeDetector is necessary when some areas of the code, angular, cannot detect changes using default detection. The good news is that to use ChangeDetector in your code, it is one line of code. All you have to do is after the changes this.detector.detectChanges(); This will also require that you previously declared the service in the constructor.

constructor(private detector: ChangeDetectorRef)

Demo:

 constructor(private detector: ChangeDetectorRef) {
  }

someMethod(){
 setInterval(() => {

          this.detector.detectChanges();

        }, 5000);
}
Dalorzo
  • 19,834
  • 7
  • 55
  • 102
0

I managed to solve this problem by emitting on success besides the file size, a null value.

private onUploadComplete(observer: Subscriber<number>, file: File) {
    return () => {
      observer.next(file.size);
      observer.next(null);
      observer.complete();

      this.filesStateService.removeProgress(fileUrl);
    };
}
Sergiu Molnar
  • 865
  • 1
  • 11
  • 22