MEAN stack ng-upload-file

本小妞迷上赌 提交于 2019-12-12 15:37:35

问题


I am currently using MEAN.js to create an app and I scaffolded a simple entity called Campaign. I would like each Campaign to have a picture associated. Therefore, I would like to change the CRUD interface to be able to upload a file to the back end.

I injected the ng-file-upload plugin to create the FE with Angular. On the Node.js side, I installed the multer plugin to help me save the file into a folder (e.g. ./uploads). The thing is that I do not quite get the flow and I was hoping for a suggestion.

Please, find below the view:

<section data-ng-controller="CampaignsController">
    <div class="page-header">
        <h1>New Campaign</h1>
    </div>
    <div class="col-md-12">
        <form class="form-horizontal" data-ng-submit="create()" novalidate>
            <fieldset>
                <div class="form-group">
                    <label class="control-label" for="name">Name</label>
                    <div class="controls">
                        <input type="text" data-ng-model="name" id="name" class="form-control" placeholder="Name" required>
                    </div>
                </div>
                <div class="form-group">
                    <button ng-file-select ng-model="token">Upload the token</button>
                    <div ng-file-drop ng-model="token" class="drop-box" 
                        drag-over-class="{accept:'dragover', reject:'dragover-err', delay:100}"
                        accept="image/*">
                                Drop image file here
                    </div>
                    <div ng-no-file-drop>Image drop is not supported for this browser.</div>
                </div>
                <div class="form-group">
                    <input type="submit" class="btn btn-default">
                </div>
                <div data-ng-show="error" class="text-danger">
                    <strong data-ng-bind="error"></strong>
                </div>
            </fieldset>
        </form>
    </div>
</section>

Then, the Angular controller action:

// Create new Campaign
    $scope.create = function() {
        // Create new Campaign object
        var campaign = new Campaigns ({
            name: this.name
        });

        $scope.$watch('token', function() {
            $scope.upload = $upload.upload({
                url: '/campaigns', //upload.php script, node.js route, or servlet url
                method: 'POST', //Post or Put
                headers: {'Content-Type': 'multipart/form-data'},
                //withCredentials: true,
                data: campaign, //from data to send along with the file
                file: $scope.token, // or list of files ($files) for html5 only
                //fileName: 'photo' // to modify the name of the file(s)                
            }).success(function (response, status) {
                // Redirect after save
                campaign.$save(function(response) {
                    $location.path('campaigns/' + response._id);

                    // Clear form fields
                    $scope.name = '';
                }, function(errorResponse) {
                    $scope.error = errorResponse.data.message;
                }); 
            }
            ).error(function (errorResponse) {
                $scope.error = errorResponse.data;
                //$scope.error = errorResponse.data.message;
            });
        });
    };

Finally, the Node.js controller portion:

 var mongoose = require('mongoose'),
        errorHandler = require('./errors'),
        multer = require('multer'),
        Campaign = mongoose.model('Campaign'),
        _ = require('lodash');

    /**
     * Create a Campaign
     */
    exports.create = function(req, res) {
        var campaign = new Campaign(req.body);
        campaign.user = req.user;

        multer({
            dest: './uploads/'
        });

        campaign.save(function(err) {
            if (err) {
                return res.status(400).send({
                    message: errorHandler.getErrorMessage(err)
                });
            } else {
                res.jsonp(campaign);
            }
        });
    };

Right now, what happens is that - when I try to upload a file - the uploader does not wait for the file to be selected, but it sends the POST request immediately (why?). Moreover, I get a 400 response.

Any suggestion would be really appreciated!

Thanks Cheers


回答1:


I partially solved the problem.

This is the new view:

<section data-ng-controller="CampaignsController">
    <div class="container">
        <div class="page-header">
            <h1>New Campaign</h1>
        </div>
        <div class="col-sm-12 col-md-4 col-md-offset-4">
            <form class="form-horizontal" data-ng-submit="create(token)" novalidate>
                <fieldset>
                    <div class="form-group">
                        <label class="control-label" for="name">Name</label>
                        <div class="controls">
                            <input type="text" data-ng-model="name" id="name" class="form-control" placeholder="Name" required>
                        </div>
                    </div>
                    <div class="form-group">
                        <label class="control-label" for="token">Token</label>
                        <div class="controls">
                            <input type="file" id="token" ng-file-select ng-model="token"/>
                            <p class="help-block">The token file must be a squared .png or .jpg image.</p>
                        </div>
                    </div>
                    <div class="form-group">
                        <div class="controls">
                            <input type="submit" class="btn btn-default col-xs-12">
                        </div>
                    </div>
                    <div class="form-group">
                        <div data-ng-show="error" class="control alert alert-danger alert-dismissible" role="alert">
                            <span data-ng-bind="error"></span>
                        </div>
                    </div>
                </fieldset>
            </form>
        </div>
    </div>
</section>

Then, the Angular controller action:

    $scope.create = function(token) {
        // Create new Campaign object
        var campaign = new Campaigns ({
            name: this.name
        });

        $scope.upload = $upload.upload({
            url: '/campaigns',
            method: 'POST',
            headers: {'Content-Type': 'multipart/form-data'},
            //withCredentials: true,
            data: { 
                campaign: JSON.stringify(campaign) 
            },
            file: token,
            //fileName: 'token' // to modify the name of the file                
        }).success(function (response, status) {
                // Redirect after save
                $location.path('campaigns/' + response._id);

                // Clear form fields
                $scope.name = '';
                $scope.token = '';
            }
        ).error(function (errorResponse) {
               $scope.error = errorResponse.data;
            }
        );
    };

I am now using node multiparty for the Node.js controller:

exports.create = function(req, res) {

var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
    //res.writeHead(200, {'content-type': 'text/plain'});
    //res.end(util.inspect({fields: fields, files: files}));
    var file = files.file[0];
    var contentType = file.headers['content-type'];
    var tmpPath = file.path;
    var extIndex = tmpPath.lastIndexOf('.');
    var extension = (extIndex < 0) ? '' : tmpPath.substr(extIndex);
    // uuid is for generating unique filenames. 
    //var fileName = uuid.v4() + extension;
    var fileName = tmpPath;
    var destPath = 'uploads/' + fileName;

    // Server side file type checker.
    if (contentType !== 'image/png' && contentType !== 'image/jpeg') {
        fs.unlink(tmpPath);
        return res.status(400).send({
            message: 'Unsupported file type'
        });
    }

    fs.rename(tmpPath, destPath, function(err) {
        if (err) {
            return res.status(400).send({
                message: 'Image is not saved'
            });
        }
        fs.unlink(tmpPath, function() {
            if (err) {
                return res.status(400).send({
                    message: 'Impossible to delete temp file'
                });
            }
        });
        console.log(destPath);
        //return res.jsonp(destPath);
    });

    var campaign = new Campaign(JSON.parse(fields.campaign[0]));
    campaign.user = req.user;

    campaign.save(function(err) {

        if (err) {
            return res.status(400).send({
                message: errorHandler.getErrorMessage(err)
            });
        } else {
            res.jsonp(campaign);
        }
    });
});

};

I still get an error, but I do not think is related with the file upload. What do you think?

/home/maurizio/Workspace/bdf-v1/node_modules/mongoose/lib/utils.js:413 throw err; ^ Error: Can't set headers after they are sent. at ServerResponse.OutgoingMessage.setHeader (http.js:691:11) at ServerResponse.header (/home/maurizio/Workspace/bdf-v1/node_modules/express/lib/response.js:592:10) at ServerResponse.send (/home/maurizio/Workspace/bdf-v1/node_modules/express/lib/response.js:144:12) at ServerResponse.jsonp (/home/maurizio/Workspace/bdf-v1/node_modules/express/lib/response.js:301:15) at Promise. (/home/maurizio/Workspace/bdf-v1/app/controllers/campaigns.server.controller.js:67:9) at Promise. (/home/maurizio/Workspace/bdf-v1/node_modules/mongoose/node_modules/mpromise/lib/promise.js:177:8)




回答2:


Using res.status(400).send... or res.jsonp() will send data back to the client starting with the headers. Your script has these statements falling through, but the subsequent ones cannot be executed since data has already been sent to the client.

The returns you have will end the execution of the method they're invoked in, but the script will just continue to the next method where it will encounter another express send(). In your case, fs.rename will send() the 400, but will encounter another send() when it reaches the campaign.save method where it will throw the error.

Take your calls of return res.status(400).send(), and instead set the message as a string variable, and make the res.status(400).send() call in your final conditional statement if an error is present.

Essentially, make sure a send() or jsonp() invocation can only be made once in your script.



来源:https://stackoverflow.com/questions/27389081/mean-stack-ng-upload-file

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!