19

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.

Cacos
  • 195
  • 1
  • 1
  • 7
  • 1
    Mean-upload has been obsoleted and is now called "upload". It is managed in - https://git.mean.io/orit/upload, but even that hasn't been updated in months now... – Tony J. Fader Mar 11 '16 at 19:08

4 Answers4

27

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!

kentcdodds
  • 27,113
  • 32
  • 108
  • 187
  • Hi thanks for your answer! I managed to make it work eventually! Only got one problem now, which is the image is uploaded before I submit the form, but I think I can manage to make work as intended. I used `angular-file-upload` as well, but (dumb me) wasn't checking Chrome's console for errors, I was missing some parentesis and it was not working (of course!). But I managed to make it work! But thanks for your answer! – Cacos Apr 15 '14 at 09:01
  • 1
    @Cacos, i'm glad you got it working. Please accept the answer so purple finding this from Google can find the answer more easily. – kentcdodds Apr 15 '14 at 12:58
  • 1
    Also, as a tip. You should probably always have the console open when developing... Pretty tough to develop without it. As you discovered... :-) – kentcdodds Apr 15 '14 at 13:00
  • Done, and yes now I've been working with the console always opened! Rookie mistakes happen :P – Cacos Apr 16 '14 at 08:50
  • I have tried following your advice and other similar tutorials however I keep getting an error. 'Var file.files[0]' crashes my app giving the message 'cannot read 0 of undefined'. Do you have any idea what my issue might be? – Charlie Tupman Apr 27 '15 at 21:31
3

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!

jmckenney
  • 41
  • 3
0

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

0

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!!

TravRob
  • 108
  • 2
  • 9