Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Creating an UploadService to Work with Flow.js and Ng-flow

DZone's Guide to

Creating an UploadService to Work with Flow.js and Ng-flow

An ng-flow directive was used to submit uploads and interact with flow.js, allowing users to utilize controller-driven parts of the app while the uploads processed.

· Web Dev Zone ·
Free Resource

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

Having the ability to upload files was an important requirement for a recent project. Users of the AngularJS application needed to include different types of images, including a standardized zip-file of images, to complete the tasks associated with the solution.

The flow.js framework and associated ng-flow AngularJS directive seemed to be a perfect fit for the upload requirements. Ng-flow is built on top of flow.js and provides a quick and easy implementation where end-users can select or drag-and-drop files onto the screen where they can be uploaded. If configured correctly, a ghost version of the images appears - showing a preview while the file(s) are being uploaded. Finally, event handling provides a mechanism where business logic could be applied (like checking for duplicates, valid image type and size, etc.) before the upload process begins.

The Limitation

Since the application functions on having images to utilize, a large number of images are part of each record within the solution. As a result, end-users spend a good portion of their time uploading files.

We thought it would be nice to allow the user to start the upload, then navigate to other pages within the application - so they could still be productive in the solution while the new files were being introduced.

During testing, we realized a limitation with our current design. After starting an upload, we single-clicked a menu option to navigate to another section of the application - only to realize the upload stopped working. The reason was that the ng-flow directive was tied to the prior controller, which was no longer in use.

We considered pushing the ng-flow aspects all the way to a parent controller, but the design of this application (which still maintained functionality from the legacy version we were replacing) made this an unattractive approach.

I began to wonder if I could introduce a service that would allow ng-flow to submit the upload to an UploadService, which then would interact with the underlying flow.js functionality directly.

UploadService Is Born

With flow.js and ng-flow already included in the AngularJS application, I created a new service called UploadService. This service included the following attributes and functions.

The UploadService has the ability to maintain a list of flow.js uploads, which allows for more than one upload to be running at a given time. As a result, not only were we allowing the end-user to start an upload and work elsewhere in the application, but they could actually start additional uploads and continue working. Of course, the more uploads that are submitted, the more system utilization is absorbed by the AngularJS application.

var instanceList = []

function getFlow() {
    return new Flow(); // jshint ignore:line
}

function UploadInstance(name, documentId) {
    this.name = name;
    this.documentId = documentId;
    this.flow = new Flow(); // jshint ignore:line

    setDefaultValues(this);

    this.broadcastFileName = false;
    this.broadcastPercentage = false;
}

function createUploadInstance(name, documentId, flow, forceCreate) {
    var thisInstance = getUploadInstance(name, documentId);
    if (thisInstance !== null && forceCreate) {
        if (flow) {
            console.log("UploadInstance by the name of " + name + " already exists, replacing flow.", flow);
            thisInstance.flow = flow;
        }

        return thisInstance;
    } else if (thisInstance === null) {
        var newInstance = new UploadInstance(name, documentId);

        if (flow) {
            newInstance.flow = flow;
        }

        instanceList.push(newInstance);
        return newInstance;
    } else {
        console.log("UploadInstance by the name of " + name + " already exists.");
        return thisInstance;
    }
}

function setDefaultValues(thisInstance) {
    thisInstance.started = false;
    thisInstance.completed = false;
    thisInstance.paused = false;
    thisInstance.cancelled = false;
    thisInstance.error = false;
    thisInstance.errorMessage = "";
    thisInstance.percentageComplete = 0;
    thisInstance.fileName = "";
}

At the page level, ng-flow is utilized to allow the user to select and/or drag-and-drop files onto the page. As part of this process, the UploadService.createUploadInstance() function is called - containing all the necessary information for flow.js to function.

<div flow-init="ctrl.flowInit"
     flow-name="ctrl.flow"
     flow-files-submitted="ctrl.flowFilesSubmitted($files, $event, ctrl.flow)">

  Upload file tags/stuff goes here...

  <button type="button" data-ng-click="ctrl.submitFlow($event, true, true)">Upload File</button>
</div>

At the controller level, these functions exist:

vm.flowInit = {
    target: UPLOAD_URL_GOES_HERE,
    permanentErrors: [404, 500, 501],
    testChunks: false,
    maxChunkRetries: 0,
    ...
    query: {
        ...
    }
};
vm.flow = UploadService.flow;

function flowFilesSubmitted($files, $event, $flow) {
    files = $files;
    flow = $flow;
}

function submitFlow($event, startUpload, setDefaultEvents) {
    var thisFlowInstance = UploadService.createUploadInstance(commonConstant.FLOWS.THIS_FLOW_CONSTANT, documentId, null, false);

    UploadService.setBroadcastFileName(thisFlowInstance, false);
    UploadService.setBroadcastPercentage(thisFlowInstance, true);
    UploadService.submitFlow(thisFlowInstance, files, $event, flow, startUpload, setDefaultEvents);
}

At that point, the UploadService.submitFlow() method is called:

function submitFlow(flowInstance, files, event, thisFlow, startUpload, setDefaultEvents) {
    setDefaultValues(flowInstance);
    setFlow(flowInstance, thisFlow, startUpload, setDefaultEvents);
}

function setFlow(thisInstance, thisFlow, startUpload, setDefaultEvents) {
    if (setDefaultEvents) {
        // Register the event listeners for upload process
        thisFlow.on('fileProgress', function (file, chunk) {
            flowFileProgress(thisInstance, file, chunk);
        });

        thisFlow.on('fileSuccess', function (file, message, chunk) {
            flowFilesSuccess(thisInstance, file, message, chunk);
        });

        thisFlow.on('uploadStart', function () {
            flowUploadStarted(thisInstance);
        });

        thisFlow.on('complete', function () {
            flowComplete(thisInstance, true);
        });

        thisFlow.on('fileError', function (file, message, chunk) {
            flowFileError(thisInstance, file, message, chunk);
        });

        thisFlow.on('error', function (message, file, chunk) {
            flowError(thisInstance, message, file, chunk);
        });
    }

    thisInstance.flow = thisFlow;

    if (startUpload) {
        service.startUpload(thisInstance);
    }
}

Based upon the values provided, built-in event handlers can be enabled (or skipped if they are already set up in the calling controller, factory, or service) and the upload can be started - or not started.

The following setters were created to allow the UploadService to broadcast file names and upload percentages to other aspects of the application:

function setBroadcastFileName(thisInstance, value) {
    if (thisInstance) {
        thisInstance.broadcastFileName = value;
    }
}

function setBroadcastPercentage(thisInstance, value) {
    if (thisInstance) {
        thisInstance.broadcastPercentage = value;
    }
}

In our instance, we decided to show an upload progress bar in the header, which included the name of the file currently being uploaded.

UploadService Described

The end result of the UploadService currently includes the following functionality:

createUploadInstance(name, documentId, flow) = initializes flow to default state, using the provided name and documentId:
    name = passed in name
    documentId = passed in documentId
    flow = new Flow(), unless a flow object is passed in.
    started = false
    completed = false
    paused = false
    cancelled = false
    error = false
    errorMessage = null
    percentageComplete = 0
    fileName = null
    broadcastFileName = false
    broadcastPercentage = false

getUploadInstance(name, documentId) = get the instance by the name and documentId provided
getAllInstances() = returns a list of all instances created
flowFileProgress(thisInstance, file, chunk) = allows a pointer into the use of this function, which can be used to broadcast file/percentage updates
isAnyInstanceStarted() = returns a boolean true if upload is in progress

submitFlow(instance, $files, $event, $flow, startUpload, setDefaultEvents) = submit UploadService instance with flow object (starts upload if startUpload is true)
startUpload(instance) = starts upload for a set flow of the specified instance
pauseUpload(instance) = pauses upload of the specified instance
cancelUpload(instance) = cancels upload of the specified instance
resumeUpload(instance) = resumes a paused upload of the specified instance
flowComplete(instance, deleteThisInstance) = marks the flow complete, which also deletes the flow instance (based upon deleteInstance value)

isStarted(instance) = flow has been started for specified instance
isCompleted(instance) = flow finished (with or without errors) for specified instance, started not toggled on completed
isPaused(instance) = flow is currently paused for specified instance
isCancelled(instance) = flow has been cancelled for specified instance
hasError(instance) = flow encountered an error for specified instance
getErrorMessage(instance) = last error message captured for specified instance
getPercentageComplete(instance) = percentage complete of last upload for specified instance
getFileName(instance) = name of the current file being uploaded for specified instance
getDocumentId(instance) = document Id for the specified instance

getProgressBarColor(instance) = based upon status of flow, returns a bootstrap progress-bar style for progress bar color for specified instance
showProgressBar(instance) = default method that can be used to hide/show progress bar for specified instance
getTextStatus(instance) = shows a text status (In-Progress, Completed, Completed with Errors, Paused, Cancelled) regarding the upload state for the specified instance.
getBroadcastFileName(instance) = returns the value of the broadcastFileName variable for the given instance, used to broadcast file name changes during uploads.
setBroadcastFileName(instance, value) = sets the broadcastFileName boolean for the given instance, used to broadcast file name changes during uploads.
getBroadcastPercentage(instance) = returns the value of the broadcastPercentage variable for the given instance, used to broadcast percentage completed changes during uploads.
setBroadcastPercentage(instance, value) = sets the broadcastPercentage boolean for the given instance, used to broadcast percentage complete changes during uploads.

As a result, end-users of the AngularJS application can submit uploads, then continue to utilize the application without the fear of the uploads being lost - gaining productivity along the way.

Have a really great day!

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

Topics:
angularjs 1.5 ,file upload angular ,angular ,service ,web dev

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}