如何管理已在表单小部件中上传的文件的 AngularJS 验证?

How to manage validation in AngularJS of a file that has already been uploaded inside a form widget?

我的 AngularJS 模型有两个属性,一个文件对象 vm.profile.file,以及一个包含对服务器上上传文件的引用的文本值 vm.profile.resumevm.profile.file 已按要求进行验证,但大小有限。但是,当更新表单时,可以接受并传递给服务器的引用是可以的,但不需要再次将文件对象设置到服务器。如果对文件的引用存在而不需要的文件对象为空,我该如何验证表单?

I'm using ng-file-upload.

表单如下所示:

这是我正在使用的一些代码:

    <div class="sj-section-content" flex="60">
        <md-card>
            <md-input-container>
                <div layout="row" layout-align="start center">
                    <md-button class="md-primary md-raised"
                               style="max-width: 150px; color: white;"
                               ngf-select
                               required
                               name="resume"
                               ngf-min-size="0MB"
                               ngf-max-size="1MB"
                               ng-model="vm.profile.file">
                        <span>{{!vm.profile.resume ? 'Select file' : 'Change'}}</span>
                    </md-button>
                    <md-button ng-if="vm.profile.resume" ng-click="vm.profile.file = {}" class="md-icon-button">
                        <md-icon md-font-icon="clear">clear</md-icon>
                    </md-button>
                </div>
                <div ng-if="vm.profile.file.name" layout="row" layout-align="start center">
                    <md-button class="md-icon-button" md-no-ink>
                        <md-icon md-font-icon="attachment">attachment</md-icon>
                    </md-button>
                    <div>{{vm.profile.file.name}}</div>
                </div>
                <div ng-messages="profileForm.resume.$error"
                     ng-if="profileForm.resume.$error && (profileForm.$submitted || profileForm.resume.$dirty)"
                     role="alert">
                    <div ng-if="profileForm.resume.$error.maxSize"
                         ng-message="maxSize">Max file size is 10MB</div>
                    <div ng-if="profileForm.resume.$error.required"
                         ng-message="required">Resume is a required field</div>
                </div>
            </md-input-container>
        </md-card>
    </div>

API 服务:

    ProfileApi.prototype.update = function update(model) {
        var deferred = $q.defer();

        Upload.upload({
            url: endpoint,
            method: 'PUT',
            data: model
        }).then(function success(response) {
            deferred.resolve(response.data);
        }, function error(response) {
            deferred.reject(response.data);
        });

        return deferred.promise;
    };

在服务器上:

exports.fileHandler = function(req, res, next) {
    var MAX_FILE_SIZE = 10 * 1000000;
    var FILE_FIELD = 'file';
    var PARENT_DIRECTORY = 'files/resumes/';

    // Validate here
    var fileFilter = function fileFilter (req, file, callback) {
        // allowed extensions .doc .docx .odt .pdf .txt
        var allowedMimeTypes = [
            'application/msword',
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'application/vnd.oasis.opendocument.text',
            'application/pdf',
            'text/plain'
        ];
        var validMimetype = allowedMimeTypes.some(function(mimetype) {
            return file.mimetype == mimetype;
        });

        if (!validMimetype) return callback(new Error('Resume is not a valid file format'));

        callback(null, true)
    };

    var fileOptions = {
        fileFilter: fileFilter,
        dest: 'tmp/',
        limits: {
            fileSize: MAX_FILE_SIZE
        }
    };
    var upload = multer(fileOptions).single(FILE_FIELD);

    upload(req, res, function(err) {
        if (err) return res.status(400).send({message: err.message});

        // We don't need a new file if req.body.resume has a value
        console.log(typeof req.body.resume);
        if (_.isEmpty(req.body.resume) && typeof req.file === 'undefined') {
            return res.status(400).send({message: 'Resume file is required'});
        }

        // Make sure that if there is a resume file but no new file, the resume exists in the file system.
        if (req.body.resume && typeof req.file === 'undefined') {
            fs.stat('client/' + req.body.resume, function(err, stats) {
                if (err) {
                    return res.status(400).send({message: 'Resume doesn\'t exist on file server'});
                } else {
                    return next();
                }
            })
        } else {
            // Let's add the file path at req.body.resume.
            // The idea is that if the save method on the model returns and error. Delete the file
            // in the tmp folder. Then return an error. If the model validates and is saved. Move the
            // file into the proper folder

            req.body.resume = PARENT_DIRECTORY + req.user._id + '/' + req.file.originalname;
            return next();
        }
    });
};

更新控制器方法:

exports.update = function(req, res) {
    var crewListing = req.app.locals.crewListing;

    // Protect information
    delete req.body.author;
    delete req.body.__v;
    delete req.body._id;

    // Merge objects
    _.merge(crewListing, req.body);

    crewListing.save({runValidators: true}, function(err, result) {
        if (req.file) {
            if (err) {
                // Delete the req file
                fs.unlink(req.file.path, function() {
                    return res.status(400).send(validationErrorHandler(err,true));
                });
            } else {
                // Move the req file
                console.log(req.file);
                fs.move(req.file.path, 'client/' + crewListing.resume, {clobber: true}, function(err) {
                    if (err) return res.status(400).send({message: 'Unexpected error has occured'})
                    return res.json(result);
                });
            }
        } else {
            if (err) return res.status(400).send(validationErrorHandler(err, true));
            return res.json(result);
        }
    })
};

文件字段上的 ng-required 指令可以是有条件的。如果简历文本字段有值,则不需要填写文件字段。

ng-required="!vm.profile.resume"