0

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);

Community
  • 1
  • 1
MrCroft
  • 3,049
  • 2
  • 34
  • 50
  • ngFileSelect uses its own class for uploading "this.upload = new NgUploaderService();" and i think u do not need u own service. u call try to grab a reference to ngFileSelect.upload to preserve it and monitor the prgress when directive is removed – Julia Passynkova Apr 30 '17 at 15:01
  • @JuliaPassynkova I will need my own service for additional stuff anyway, regarding the uploads, so I figured I might as well keep track of all the uploads that are "in progress" as well and check with my the service in any other component I might need that info. Now, regarding what you wrote... I was thinking of injecting `NgUploaderService` in my service... and use it somehow. But I have no clue how, can't figure out how from the ngx-uploader demo and example. Plus, the ngFileSelect and ngFileDrop still need `[uploadInput]="uploadInput"`. Without that, I don't think they'd work... – MrCroft Apr 30 '17 at 16:43
  • NgUploaderService is not a service but class that is create by directives with 'new NgUploaderService()' – Julia Passynkova Apr 30 '17 at 16:46
  • I'm afraid I'm not the person to abstract from a few words. I'm still as clueless as I was before asking the question. If anybody has or knows a link to a working example (code), that would be great. – MrCroft Apr 30 '17 at 20:26

0 Answers0