There is a similar question here. However, the accepted (and only) answer doesn't explain how to actually do it, but only describes the flux architecture with an example that has nothing to do with the specific scenario of using ngx-uploader.
My issue is about using ngx-uploader in a service and keep getting progress data while navigating through the app (meaning that the component which contains the file input and the file drop area might get destroyed after an upload starts).
I've tried the latest demo, used the code in the example here. Only, instead of having that code in a component, I tried putting it in a service (so it would persist, even after we navigate to other routes and the component with the file input gets destroyed).
Then, in my component, I just wanted to use the service.
upload.component.ts: (only ngx-uploader related lines)
public uploadInput;
constructor(private uploadService: UploadService) {
this.uploadInput = this.uploadService.uploadInput;
}
onUploadOutput(output: UploadOutput): void {
this.uploadService.onUploadOutput(output);
}
/**
* No need to use this, method because the upload is set to auto in the service.
*/
startUpload(): void { // manually start uploading
const event: UploadInput = {
type: 'uploadAll',
url: 'http://localhost:3000/upload',
method: 'POST',
data: { hash: this.uploadService.generateHash() },
concurrency: 0 // set sequential uploading of files with concurrency 1
}
this.uploadService.uploadInput.emit(event);
}
cancelUpload(id: string): void {
this.uploadService.uploadInput.emit({ type: 'cancel', id: id });
}
upload.component.html:
<section class="upload-area"
ngFileDrop
(uploadOutput)="onUploadOutput($event)"
[uploadInput]="uploadInput">
<h3 i18n>Drag files here to upload</h3>
<p><span i18n>or</span> <span i18n>Click to choose a file</span></p>
<label class="upload-button">
<input type="file"
ngFileSelect
(change)="onInputChange($event)"
(uploadOutput)="onUploadOutput($event)"
[uploadInput]="uploadInput"
multiple
class="invisible" />
</label>
</section>
And, the upload service mainly has the code grabbed from the example here
upload.service.ts:
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { UploadFile, UploadInput, humanizeBytes, UploadOutput } from 'ngx-uploader';
import { SnackbarService } from './snackbar.service';
@Injectable()
export class UploadService {
private uploadUrl = 'http://localhost:3000/upload';
/**
* Store data about all uploads which are "in progress", so we can share
* this data with any component that might require it
* For example, list the uploads "in progress" in the queue component (/queue)
* or enter some details about the upload (/queue/:uploadId)
* which will be sent to the server for saving
* (one upload means one or more files that were added in a single Drag&drop operation or selected in a single Browse operation, that should be uploaded as a group)
*/
public uploads = [];
/** ngx-uploader demo code { >>> */
formData: FormData;
files: UploadFile[];
uploadInput: EventEmitter<UploadInput>;
humanizeBytes: Function;
dragOver: boolean;
/** <<< } ngx-uploader demo code */
constructor(
private router: Router, // Will be used to automatically navigate to '/queue/:uploadId' right after an upload starts, to enter some info about the upload, even while it's uploading
private snackBarService: SnackbarService // Will be used to show different messages
) {
/** ngx-uploader demo code { >>> */
this.files = []; // local uploading files array
this.uploadInput = new EventEmitter<UploadInput>(); // input events, we use this to emit data to ngx-uploader
this.humanizeBytes = humanizeBytes;
/** <<< } ngx-uploader demo code */
}
onUploadOutput(output: UploadOutput): void {
/** ngx-uploader demo code { >>> */
console.log(output); // lets output to see what's going on in the console
if (output.type === 'allAddedToQueue') { // when all files added in queue
// uncomment this if you want to auto upload files when added
const event: UploadInput = {
type: 'uploadAll',
url: 'http://localhost:3000/upload',
method: 'POST',
data: { hash: this.generateHash() },
concurrency: 0
};
this.uploadInput.emit(event);
} else if (output.type === 'addedToQueue') {
this.files.push(output.file); // add file to array when added
} else if (output.type === 'uploading') {
// update current data in files array for uploading file
const index = this.files.findIndex(file => file.id === output.file.id);
this.files[index] = output.file;
} else if (output.type === 'removed') {
// remove file from array when removed
this.files = this.files.filter((file: UploadFile) => file !== output.file);
} else if (output.type === 'dragOver') { // drag over event
this.dragOver = true;
} else if (output.type === 'dragOut') { // drag out event
this.dragOver = false;
} else if (output.type === 'drop') { // on drop event
this.dragOver = false;
/** <<< } ngx-uploader demo code */
} else if (output.type === "start") {
this.snackBarService.flashMessage('Upload starting...', ['snackbar-info']);
/**
* Here I would like to navigate to /queue/:hash to enter some details
* about the uploaded content, while it's uploading
* this.router.navigateByUrl(`/queue/${hash}`);
*/
}
}
/**
* Generate a different hash for each batch of uploads to keep track of batches;
* Used for queue navigation (/queue/:hash)
*
* Also, needed server side to be able to continue uploading photos to the
* same album, while you edit some info on the queue page
* @param length
* @param chars
*/
generateHash(length: number = 6, chars: string = 'abcdefghijklmnopqrstuvwxyz0123456789') {
let hash = '';
for (let i = length; i > 0; i--) {
hash += chars[Math.floor(Math.random() * chars.length)];
}
return hash;
}
}
Problem is:
Even though the uploader code is in a service, it is still somehow coupled with the file input in my component and it stops logging to the console as soon as we navigate away from the dashboard/home component (where we have the upload component). I don't know how to decouple that.
Also, if I navigate to the route that renders the upload component, after having navigated away previously, I get an error: Error: Uncaught (in promise): ObjectUnsubscribedError: object unsubscribed
.
Desired behavior:
When the user navigates to any other route in the app (after having started an upload), I still need the upload service to keep logging to the console (just to keep things simple).
The actual plan is to store all uploads which are in progress in an array in the service and have an event emitter to which I can subscribe and listen to in any other component in my app, so I can show the user any data regarding the uploads in progress, anywhere else I might choose to in the app (like in http://localhost:4200/queue
for example, or http://localhost/queue/upload-id
).
I've also made a repo and put everything there (client and server);