Adding a picture to the MEAN.JS sample with Angular-file-upload
Asked Answered
M

4

5

I am using MEAN.JS (https://github.com/meanjs/mean) and angular-file-upload (https://github.com/danialfarid/angular-file-upload).

The "Article" sample provided by MEAN.JS contains two fields named "title" and "content". I want to modify that and add a "picture" field allowing a user to upload a picture.

I understand that I have to modify 3 files in MEAN.JS:

~myproject/app/models/article.server.model.js 
~myproject/public/modules/articles/controllers/articles.client.controller.js
~myproject/public/modules/articles/views/create-article.client.view.html

However, I can not modify them successfully.

Mendacity answered 19/1, 2015 at 16:36 Comment(1)
What changes have you tried to make so far to those files? While it's good that you have identified them, there isn't much to go from if we don't know what you have tried.Anus
W
7

My solution uses angular-file-upload on the client and uses connect-multiparty to handle the file upload.

The images are stored directly in the database which limits their size. I have not included the required code to check the image size.

set up

 bower install ng-file-upload --save
 bower install ng-file-upload-shim --save 

 npm i connect-multiparty
 npm update

all.js add angular-file-upload scripts

            ...
            'public/lib/ng-file-upload/FileAPI.min.js', 
            'public/lib/ng-file-upload/angular-file-upload-shim.min.js',
            'public/lib/angular/angular.js', 
            'public/lib/ng-file-upload/angular-file-upload.min.js',
            ...

config.js Inject angular-file-upload dependency

...
var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ui.router', 'ui.bootstrap', 'ui.utils', 'angularFileUpload'];
...

article.client.controller.js Use angular-file-upload dependency

angular.module('articles').controller('ArticlesController', ['$scope', '$timeout',  '$upload', '$stateParams', '$location', 'Authentication', 'Articles',
function($scope, $timeout, $upload, $stateParams, $location, Authentication, Articles) {
    $scope.fileReaderSupported = window.FileReader !== null;



    // Create new Article
            $scope.create = function(picFile) {
          console.log('create');
                      console.log(picFile);
        var article = new Articles({
            title: this.title,
            content: this.content,
            image: null
        });

         console.log(article);
         $upload.upload({
            url: '/articleupload', 
            method: 'POST', 
            headers: {'Content-Type': 'multipart/form-data'},
            fields: {article: article},
            file: picFile,               
        }).success(function (response, status) {
              $location.path('articles/' + response._id);

            $scope.title = '';
            $scope.content = '';
        }).error(function (err) {
                $scope.error = err.data.message;
        });

    };

    $scope.doTimeout = function(file) {
         console.log('do timeout');
        $timeout( function() {
                var fileReader = new FileReader();
                fileReader.readAsDataURL(file);
             console.log('read');
                fileReader.onload = function(e) {
                    $timeout(function() {
                        file.dataUrl = e.target.result;
                         console.log('set url');
                    });
                };
            });
    };


    $scope.generateThumb = function(file) {
        console.log('generate Thumb');
    if (file) {
        console.log('not null');
         console.log(file);
        if ($scope.fileReaderSupported && file.type.indexOf('image') > -1) {
            $scope.doTimeout(file);
          }
      }
   };
}

create-article.client.view.html update the create view to handle file selection and upload

<section data-ng-controller="ArticlesController">
<div class="page-header">
    <h1>New Article</h1>
</div>
<div class="col-md-12">
    <form name="articleForm" class="form-horizontal" data-ng-submit="create(picFile)" novalidate>
        <fieldset>
            <div class="form-group" ng-class="{ 'has-error':   articleForm.title.$dirty && articleForm.title.$invalid }">
                <label class="control-label" for="title">Title</label>
                <div class="controls">
                    <input name="title" type="text" data-ng-model="title" id="title" class="form-control" placeholder="Title" required>
                </div>
            </div>
            <div class="form-group">
                <label class="control-label" for="content">Content</label>
                <div class="controls">
                    <textarea name="content" data-ng-model="content" id="content" class="form-control" cols="30" rows="10" placeholder="Content"></textarea>
                </div>
            </div>
            <div class="form-group">
                <label class="control-label" for="articleimage">Article Picture</label>
                <div class="controls">
                     <input id="articleimage" type="file" ng-file-select="" ng-model="picFile" name="file" accept="image/*" ng-file-change="generateThumb(picFile[0], $files)" required="">
                    <br/>
                    <img ng-show="picFile[0].dataUrl != null" ng-src="{{picFile[0].dataUrl}}" class="img-thumbnail" height="50" width="100">
                    <span class="progress" ng-show="picFile[0].progress >= 0">      
                        <div style="width:{{picFile[0].progress}}%" ng-bind="picFile[0].progress + '%'" class="ng-binding"></div>
                    </span> 
                    <span ng-show="picFile[0].result">Upload Successful</span>
                </div>
            </div>
            <div class="form-group">
                <input type="submit" class="btn btn-default" ng-disabled="!articleForm.$valid" ng-click="uploadPic(picFile)">
            </div> 
            <div data-ng-show="error" class="text-danger">
                <strong data-ng-bind="error"></strong>
            </div>
        </fieldset>
    </form>
</div>
</section>

view-article.client.view.html update list view to include image

<img ng-src="data:image/jpeg;base64,{{article.image}}" id="photo-id" width="200" height="200"/>

list-articles.client.view.html update view to include imgae

<img ng-src="data:image/jpeg;base64,{{article.image}}" id="photo-id" width="40" height="40"/>

article.server.model.js Add image to database model

...
image: {
    type: String,
    default: ''
},
...

article.server.routes.js Add new route for upload use connect-multiparty

...
multiparty = require('connect-multiparty'),
multipartyMiddleware = multiparty(),
...
app.route('/articleupload')
    .post(users.requiresLogin, multipartyMiddleware, articles.createWithUpload);
...

article.server.controller.js Handle new route for upload require fs

...
fs = require('fs'),
...
/**
 * Create a article with Upload
 */
exports.createWithUpload = function(req, res) {
 var file = req.files.file;
 console.log(file.name);
 console.log(file.type);
 console.log(file.path);
 console.log(req.body.article);

var art = JSON.parse(req.body.article);
var article = new Article(art);
article.user = req.user;

fs.readFile(file.path, function (err,original_data) {
 if (err) {
      return res.status(400).send({
            message: errorHandler.getErrorMessage(err)
        });
  } 
    // save image in db as base64 encoded - this limits the image size
    // to there should be size checks here and in client
  var base64Image = original_data.toString('base64');
  fs.unlink(file.path, function (err) {
      if (err)
      { 
          console.log('failed to delete ' + file.path);
      }
      else{
        console.log('successfully deleted ' + file.path);
      }
  });
  article.image = base64Image;

  article.save(function(err) {
    if (err) {
        return res.status(400).send({
            message: errorHandler.getErrorMessage(err)
        });
    } else {
        res.json(article);
    }
  });
});
};
...
Wiley answered 2/4, 2015 at 6:9 Comment(6)
I am unable to get this to work, do you have a solution that stores the file to the server instead of the database?Johm
What goes wrong, provide error messages and code and I may be able to help.Wiley
please see my solution below @john purnellJohm
Sorry Charlie I am off mean at the moment, so just a quick answer. see the angular-file-upload doco e.g. Upload multiple files: Only for HTML5 FormData browsers (not IE8-9) if you pass an array of files to file option it will upload all of them together in one request. Non-html5 browsers due to flash limitation will still upload array of files one by one in a separate request. You should iterate over files and send them one by one if you want cross browser solution.Wiley
Prunell, thanks John, I will look into this and let you know how I get on.Johm
Hi, I seem to be getting a whitescreen when i add the dependacy 'angularFileUpload' to the list. It gives a Error: [$injector:nomod] Module 'angularFileUpload' is not available! error.Sandstone
D
2

Thanks Charlie Tupman for this last solution which works very well.

I simply add the dependencies :

...
multiparty = require('multiparty'),
uuid = require('uuid'),
...

before the exports function and changed two lines to improve the behavior:

...
var destPath = './public/uploads/' + fileName;

article.image = '/uploads/' + fileName;
...

which resolve in:

exports.createWithUpload = function(req, res) {

    var form = new multiparty.Form();
    form.parse(req, function(err, fields, files) {

        var file = req.files.file;
        console.log(file.name);
        console.log(file.type);
        console.log(file.path);
        console.log(req.body.article);

        var art = JSON.parse(req.body.article);
        var article = new Article(art);
        article.user = req.user;
        var tmpPath = file.path;
        var extIndex = tmpPath.lastIndexOf('.');
        var extension = (extIndex < 0) ? '' : tmpPath.substr(extIndex);
        var fileName = uuid.v4() + extension;
        var destPath = './public/uploads/' + fileName;

        article.image = '/uploads/' + fileName;

        var is = fs.createReadStream(tmpPath);
        var os = fs.createWriteStream(destPath);

        if(is.pipe(os)) {
            fs.unlink(tmpPath, function (err) { //To unlink the file from temp path after copy
                if (err) {
                    console.log(err);
                }
            });
            article.save(function(err) {
                if (err) {
                    return res.status(400).send({
                        message: errorHandler.getErrorMessage(err)
                    });
                } else {
                    res.jsonp(article);
                }
            });
        } else
            return res.json('File not uploaded');
    });

};
Dangelo answered 5/6, 2015 at 21:0 Comment(0)
C
1

My working solution in MEAN.js

server model:

image:{ 
    type: String, 
    default: ''
},

server controller:

var item = new Item(JSON.parse(req.body.item));
item.user = req.user;
if(req.files.file)
    item.image=req.files.file.name;
else
    item.image='default.jpg';

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

server route: (requires multer: "npm install multer --save")

var multer  = require('multer');
app.use(multer({ dest: './public/uploads/'}));

frontend angular controller:

    $scope.image='';

    $scope.uploadImage = function(e){
        console.log(e.target.files[0]);
        $scope.image=e.target.files[0];

    };



    // Create new Item
    $scope.create = function() {
        // Create new Item object
        var item = new Items ({
            name: this.name,
            bought: this.bought,
            number: this.number,
            description: this.description,
            warranty: this.warranty,
            notes: this.notes


        });

        ItemsService.saveItem(item,$scope.image);

    };

service which send's the request:

.factory('ItemsService', ['$http','$rootScope', function($http, $rootScope) 
{
    var service={};

    service.saveItem = function(item, image)
    {

        var fd = new FormData();
        fd.append('file', image);
        fd.append('item', JSON.stringify(item));
        $http.post('items/', fd, {
            transformRequest: angular.identity,
            headers: {'Content-Type': undefined}
        })
        .success(function(){
            console.log('success add new item');
        })
        .error(function(e){
            console.log('error add new item', e);
        });


    };

    return service;

}

]);

html view:

           <div class="form-group">
                <label class="control-label" for="name">Image</label>
                <div class="controls">
                    <input type="file"  data-ng-model="image" id="image" my-file-upload="uploadImage" required>
                     {{selectedFile.name}}
                </div>
            </div>
Chimborazo answered 26/1, 2015 at 10:8 Comment(4)
Hi, I have tried to implement your solution however I just get an 'ItemsService is not defined' error in my module controller. Do you know what I'm doin wrong?Johm
I add the factory to my client service in several different ways but I get 'ItemsService is not defined: public\modules\testcruds\controllers\testcruds.client.controller. ItemsService.saveItem(testcrud,$scope.image); 'ItemsService' is not defined.Johm
make sure, that you injected service in controller dependencies: ~ angular.module(...).controller('...',['dependencies'Chimborazo
I implemented verbatim, but the $scope.uploadImage = function(e) is not being triggered when an image is selected in the browser, I can't see what I am missing?Clavicembalo
J
1

@john prunell Thank you so much for this, I finally figured it out (took me over a week but i am now much more comfortable with the stack) I have forked it to get it to upload the file to a folder:

'exports.createWithUpload =     function(req, res) {

var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {

 var file = req.files.file;
 console.log(file.name);
 console.log(file.type);
 console.log(file.path);
 console.log(req.body.article);

var art = JSON.parse(req.body.article);
var article = new Article(art);
article.user = req.user;
var tmpPath = file.path;
var extIndex = tmpPath.lastIndexOf('.');
var extension = (extIndex < 0) ? '' : tmpPath.substr(extIndex);
var fileName = uuid.v4() + extension;
var destPath = './uploads/' + fileName;

article.image = fileName;

var is = fs.createReadStream(tmpPath);
var os = fs.createWriteStream(destPath);

if(is.pipe(os)) {
    fs.unlink(tmpPath, function (err) { //To unlink the file from temp path after copy
        if (err) {
            console.log(err);
        }
    });
    article.save(function(err) {
    if (err) {
        return res.status(400).send({
            message: errorHandler.getErrorMessage(err)
        });
    } else {
        res.jsonp(article);
    }
});
}else
    return res.json('File not uploaded');
});

};

What I would like to do now is fork your solution to allow uploading of multiple images in the same way, if you have any insight into how to do this that would be amazing, I will let you know how I get on.

Johm answered 8/5, 2015 at 16:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.