Search code examples
javascriptangularjscsvpapaparseng-file-upload

Angular algorithm: stop ngFileUpload double looping?


I have a controller:

app.controller('FileUploadController', ['$scope', 'Upload', '$timeout', function ($scope, Upload, $timeout) {
    $scope.$watch('files', function () {
        $scope.upload($scope.files);
    });

    $scope.$watch('file', function () {
        if ($scope.files !== null) {
            $scope.upload([$scope.file]);
        }
    });

    $scope.upload = function (files) {
        $scope.log = '';
        if (files && files.length) {
            $scope.numberOfFiles = files.length;
            for (var i = 0; i < files.length; i++) {
                var file = files[i];
                if (file && !file.$error) {
                    Upload.upload({
                        url: 'http://localhost/Reconcile/index.php',
                        file: file
                        // fileName: i // to modify the name of the file(s)
                    }).progress(function (evt) {
                        var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
                        $scope.log = progressPercentage;
                    }).success(function (data, status, headers, config) {
                        for (var j = 0; j < 2; j++) {
                            $scope.parsePapa(files[j], j);
                        }
                    }).error(function (data, status, headers, config) {
                        $scope.log = 'error status: ' + status;
                    });
                }   
            }
        }
    };
}]);

Which is basically being used to allow for a drag n drop of a csv file via ngFileUpload which is then parsed by Papa Parse. In my call to Papa Parse (located at $scope.parsePapa), I need the second argument to send information about which file was uploaded (just an index is fine) for organizational purposes later, with the first argument being the file itself. The problem is, if for the success function, I did something like this:

}).success(function (data, status, headers, config) {
    $scope.parsePapa(files[i], i);
}

What happens is that success is only called once both files have finished uploading. By that time, i is already at 2 (in the case of 2 files: 0 for the first time through, 1 for the second file, and 2 for after the second file). If I used the above code, it does not return a file, since the index of files[2] has nothing stored in it if two files were uploaded.

To fix this, I used a loop within the for loop that will iterate through the length of files and print out each file contained in files. But, since there are going to be multiple files, the current behavior is actually (in the case of 2 files being uploaded, for example) to parse each file twice. I am parsing csv files with 1,000,000 rows (which are later compared), so uploading twice is affecting performance.

In short I am looking for a way to send each file to $scope.parsePapa(file, fileNumber), only once.

Any help would be greatly appreciated. I am totally new to Angular, so apologies in advance if this is something simple I missed.


EDIT: danial solved the problem (see below). if anyone is interested, the final code looked like this:

app.controller('FileUploadController', ['$scope', 'Upload', '$timeout', function ($scope, Upload, $timeout) {
$scope.$watch('files', function () {
    $scope.upload($scope.files);
});

function uploadFile(file, index) {
    Upload.upload({
        url: 'http://localhost/Reconcile/index.php',
        file: file
        // fileName: i // to modify the name of the file(s)
    }).progress(function (evt) {
        var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
        $scope.log = progressPercentage;
    }).success(function (data, status, headers, config) {
        $scope.parsePapa(file, index);
    }).error(function (data, status, headers, config) {
        $scope.log = 'error status: ' + status;
    });
}

$scope.upload = function (files) {
    $scope.log = '';
    if (files && files.length) {
        $scope.numberOfFiles = files.length;
        for (var i = 0; i < files.length; i++) {
            var file = files[i];
            if (file && !file.$error) {
                uploadFile(file, i);
            }
        }
    }
};

}]);


Solution

  • If you want to know the index of the uploaded file you can do like this:

     function uploadFile(file, index) {
        Upload.upload({....}).success(function() {
           $scope.parsePapa(file, index);
        })...;
     }
    
     $scope.upload = function (files) {
        if (files && files.length) {
           var file = files[i];
           if (file && !file.$error) {
              uploadFile(file, i);
           }
        }
     }
    

    Also you only need one of these two watches if you allow multiple files the first one is enough:

    $scope.$watch('files', function () {
        $scope.upload($scope.files);
    });
    
    $scope.$watch('file', function () {
        if ($scope.files !== null) {
            $scope.upload([$scope.file]);
        }
    });