Question

I've recently started programming with the MEAN Stack, and I'm currently implementing some sort of social network. Been using the MEAN.io framework to do so. My main problem right now is getting the file upload to work, because what I want to do is receive the file from the form into the AngularJS Controller and pass it along with more info's to ExpressJS so I can finally send everything to MongoDB. (I'm building a register new user form).

I dont want to store the file itself on the database but I want to store a link to it.

I've searched dozens of pages on google with different search queries but I couldn't find anything that I could understand or worked. Been searching for hours to no result. That's why I've came here.

Can anyone help me with this?

Thanks :)

EDIT: Maybe a bit of the code would help understand.

The default MEAN.io Users Angular controller which I'm using as foundation has this:

$scope.register = function(){
        $scope.usernameError = null;
        $scope.registerError = null;
        $http.post('/register', {
            email: $scope.user.email,
            password: $scope.user.password,
            confirmPassword: $scope.user.confirmPassword,
            username: $scope.user.username,
            name: $scope.user.fullname
        })//... has a bit more code but I cut it because the post is the main thing here.
    };

What I want to do is: Receive a file from a form, onto this controller and pass it along with email, password, name, etc, etc and be able to use the json on expressjs, which sits on the server side. The '/register' is a nodejs route so a server controller which creates the user (with the user schema) and sends it to the MongoDB.

Was it helpful?

Solution

I recently did something just like this. I used angular-file-upload. You'll also want node-multiparty for your endpoint to parse the form data. Then you could use s3 for uploading the file to s3.

Here's some of my [edited] code.

Angular Template

<button>
  Upload <input type="file" ng-file-select="onFileSelect($files)">
</button>

Angular Controller

$scope.onFileSelect = function(image) {
  $scope.uploadInProgress = true;
  $scope.uploadProgress = 0;

  if (angular.isArray(image)) {
    image = image[0];
  }

  $scope.upload = $upload.upload({
    url: '/api/v1/upload/image',
    method: 'POST',
    data: {
      type: 'profile'
    },
    file: image
  }).progress(function(event) {
    $scope.uploadProgress = Math.floor(event.loaded / event.total);
    $scope.$apply();
  }).success(function(data, status, headers, config) {
    AlertService.success('Photo uploaded!');
  }).error(function(err) {
    $scope.uploadInProgress = false;
    AlertService.error('Error uploading file: ' + err.message || err);
  });
};

Route

var uuid = require('uuid'); // https://github.com/defunctzombie/node-uuid
var multiparty = require('multiparty'); // https://github.com/andrewrk/node-multiparty
var s3 = require('s3'); // https://github.com/andrewrk/node-s3-client

var s3Client = s3.createClient({
  key: '<your_key>',
  secret: '<your_secret>',
  bucket: '<your_bucket>'
});

module.exports = function(app) {
  app.post('/api/v1/upload/image', function(req, res) {
    var form = new multiparty.Form();
    form.parse(req, function(err, fields, files) {
      var file = files.file[0];
      var contentType = file.headers['content-type'];
      var extension = file.path.substring(file.path.lastIndexOf('.'));
      var destPath = '/' + user.id + '/profile' + '/' + uuid.v4() + extension;

      var headers = {
        'x-amz-acl': 'public-read',
        'Content-Length': file.size,
        'Content-Type': contentType
      };
      var uploader = s3Client.upload(file.path, destPath, headers);

      uploader.on('error', function(err) {
        //TODO handle this
      });

      uploader.on('end', function(url) {
        //TODO do something with the url
        console.log('file opened:', url);
      });
    });
  });
}

I changed this from my code, so it may not work out of the box, but hopefully it's helpful!

OTHER TIPS

Recently a new package was added to the list of packages on mean.io. It's a beauty!

Simply run:

$ mean install mean-upload

This installs the package into the node folder but you have access to the directives in your packages.

http://mean.io/#!/packages/53ccd40e56eac633a3eee335

On your form view, add something like this:

    <div class="form-group">
        <label class="control-label">Images</label>
        <mean-upload file-dest="'/packages/photos/'" upload-file-callback="uploadFileArticleCallback(file)"></mean-upload>
        <br>
        <ul class="list-group">
            <li ng-repeat="image in article.images" class="list-group-item">
                {{image.name}}
                <span class="glyphicon glyphicon-remove-circle pull-right" ng-click="deletePhoto(image)"></span>
            </li>
        </ul>
    </div>

And in your controller:

    $scope.uploadFileArticleCallback = function(file) {
      if (file.type.indexOf('image') !== -1){
          $scope.article.images.push({
            'size': file.size,
            'type': file.type,
            'name': file.name,
            'src': file.src
          });
      }
      else{
          $scope.article.files.push({
            'size': file.size,
            'type': file.type,
            'name': file.name,
            'src': file.src
          });
      }
    };

    $scope.deletePhoto = function(photo) {
        var index = $scope.article.images.indexOf(photo);
        $scope.article.images.splice(index, 1);
    }

Enjoy!

Mean-upload has been obsoleted and is now called "upload". It is managed in - https://git.mean.io/orit/upload

I know this post is old. I came across it and @kentcdodds had an answer that i really liked, but the libraries he used are now out of date and I could not get them to work. So after some research i have a newer similar solution I want to share.

HTML using ng-upload

<form >
    <div style="margin-bottom: 15px;">
        <button type="file" name="file" id="file" ngf-select="uploadFiles($file, $invalidFiles)" accept="image/*" ngf-max-height="1000" ngf-max-size="1MB">Select File</button>
    </div>
</form>

INCLUDE ng-upload module

download it, references the files and include the module

var app = angular.module('app', ['ngFileUpload']);

this will give you access to the Upload service.

Controller code

$scope.uploadFiles = function(file, errFiles) {
    $scope.f = file;
    $scope.errFile = errFiles && errFiles[0];
    if (file) {
        file.upload = Upload.upload({
            url: 'you-api-endpoint',
            data: {file: file}
        });

        //put promise and event watchers here if wanted
    }   
};

NODE api code

All the code below is in a separate route file which is required in my main server.js file.

require('./app/app-routes.js')(app, _);

var fs = require('fs');
var uuid = require('uuid');
var s3 = require('s3fs'); // https://github.com/RiptideElements/s3fs
var s3Impl = new s3('bucketname', {
    accessKeyId: '<your access key id>',
    secretAccessKey: '< your secret access key >'
});

var multiparty = require('connect-multiparty');
var multipartyMiddleware = multiparty();

module.exports = function(app, _) {
    app.use(multipartyMiddleware);


    app.post('/your-api-endpoint',function(req, res){

    var file = req.files.file; // multiparty is what allows the file to to be accessed in the req
    var stream = fs.createReadStream(file.path);
    var extension = file.path.substring(file.path.lastIndexOf('.'));
    var destPath = '/' + req.user._id + '/avatar/' +  uuid.v4() + extension;
    var base = 'https://you-bucket-url';
    return s3Impl.writeFile(destPath, stream, {ContentType: file.type}).then(function(one){
        fs.unlink(file.path);
        res.send(base + destPath); 
    });
});

All i was trying to do was upload a unique avatar for a user. Hope this helps!!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top