Uploading picture with Angular, Express, Mongoose - angularjs

I'm trying to upload and store picture with Mongoose, Express and Angular. I've picked here the next solution:
.directive('fileModel', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.bind('change', function(){
$parse(attrs.fileModel).assign(scope,element[0].files)
scope.$apply();
});
}
};
}])
And the next function in controller:
$scope.uploadFile=function(){
var fd = new FormData();
angular.forEach($scope.files,function(file){
fd.append('file',file);
});
$http.post('http://' + host + ':3000/users/' + $scope.selectedTask._id,fd,
{
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
}).success(function(d){
console.log('yes');
})
}
And html:
<input type = "file" file-model="files" multiple/>
<button ng-click = "uploadFile()">upload me</button>
<li ng-repeat="file in files">{{file.name}}</li>
But for some reason all I'm getting in my endpoint is an empty request object. I'm checking it with the following code in express.js:
user.route('/users/:id')
.post(function (req, res, next) {
console.log(req.body);
})
I think the problem is that I don't know how to store something that is larger then 16MB.

In this example you will see how to store the file you are sending in to your server directory and then pick them up from there and save them. You can also directly save them.
First you pick up the file using angular, if you want you can
check here for more details.
Here is my small example the code is in jade.
input(type="file" name="file" onchange="angular.element(this).scope().selectFile(this.files)")
button(ng-click='savePhoto()') Save
In your angular controller
$scope.savePhoto = function () {
var fd = new FormData();
fd.append("file", $scope.files[0]);
)) ;
$http.post("/xxx/photos", fd, {
withCredentials: true,
headers: { 'Content-Type': undefined },
transformRequest: angular.identity
}).success(function (data) {
$scope.image = data; // If you want to render the image after successfully uploading in your db
});
};
Install multer using npm in your back end. And then in app.js you can set up a middleware to collect the files you are sending in. Just do console.log(req) here to check if you are getting the files till here. Multer does the magic here.
app.use(multer({
dest: path.join(__dirname, 'public/assets/img/profile'),
rename: function (fieldname, filename, req, res) {
console.log(req)// you will see your image url etc.
if(req.session.user) return req.session.user.id;
}
}));
So here the image will be stored in this path (public/assets/img/profile) in your server.
Now you pick up the file from this server and add to your db.
var path = require('path');
var imgPath =path.join(__dirname, '../public/assets/img/profile/' + id + '.jpg'); // this is the path to your server where multer already has stored your image
console.log(imgPath);
var a ;
a = fs.readFileSync(imgPath);
YourSchema.findByIdAndUpdate( id, {
$set:
{'img.data' : a,
'img.contentType' : 'image/png' }
}, function(err, doc) {
if (err)console.log("oi");
}
);
//In case you want to send back the stored image from the db.
yourSchema.findById(id, function (err, doc) {
if (err)console.log(err);
var base64 = doc.img.data.toString('base64');
res.send('data:'+doc.img.contentType+';base64,' + base64);
});
In your schema store the image in type Buffer
img: { data: Buffer}

Related

$http.post: Large files do not work

I am trying to upload files through my web app using the following code.
View:
<form name="uploadForm" class="form-horizontal col-sm-12">
<div class="col-sm-4">
<input type="file" ng-model="rsdCtrl.viewData.file" name="file"/>
</div>
<div class="col-sm-4">
<button class="btn btn-success" type="submit" ng-click="uploadFile()">Upload</button>
</div>
</form>
Controller:
function uploadFile(){
if (uploadForm.file.$valid && file) {
return uploadService.upload(vd.file, "Convictions Calculator", "PCCS").then(function(response){
/* Some stuff */
}).catch(handleServiceError);
}
}
uploadService:
(function (){
'use strict';
angular.module('cica.common').service('uploadService', ['$http', '$routeParams', uploadService]);
function uploadService($http, $routeParams) {
this.upload = function (file, name, type) {
const fd = new FormData();
fd.append('document', file);
fd.append('jobId', $routeParams.jobId);
fd.append('documentRename', name);
fd.append('documentType', type);
return $http.post('/document/upload', fd, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
}).catch(function(err){
handleHttpError('Unable to upload document.', err);
});
};
}
})();
routes.js:
'POST /document/upload': {controller: 'DocumentController', action: 'uploadDocument'},
DocumentController:
"use strict";
const fs = require('fs');
module.exports = {
uploadDocument: function (req, res) {
console.log(req.allParams()); //Inserted as part of debugging
const params = req.allParams();
req.file('document').upload({
// don't allow the total upload size to exceed ~100MB
maxBytes: 100000000
}, function whenDone(err, uploadedFiles) {
if (err) {
return res.serverError(err);
}
// If no files were uploaded, respond with an error.
else if (uploadedFiles.length === 0) {
return res.serverError('No file was uploaded');
} else {
const filePath = uploadedFiles[0].fd;
const filename = uploadedFiles[0].filename;
return fs.readFile(filePath, function (err, data) {
if (err) {
return res.serverError(err);
} else {
const jobId = params.jobId;
const jobVars =
{
filePath: results.filePath,
fileName: params.documentRename,
fileType: params.documentType
};
return DocumentService.uploadConvictions(req.session.sessionId, jobId, jobVars).then(function (response) {
return res.send("Document uploaded.");
}).catch(function (err) {
return res.serverError(err);
});
}
});
}
});
},
If I upload a .jpeg (around 11kB) the upload works exactly as expected, however, if I try to upload a larger .jpeg (around 170kB) it falls over. There is no immediate error thrown/caught though, what happens is the formData object created in the upload service seems to lose its data. If I breakpoint on its value, it returns empty for the larger file, which eventually causes an error when the function tries to use these variables further on. Is there some kind of limit set to the size of a file you can upload via this method, or have I configured this incorrectly?
I take the chance and assume you are using bodyParser as middleware. bodyParser has a default limit of 100kb. Look at node_modules/body-parser/lib/types/urlencoded.js :
var limit = typeof options.limit !== 'number'
? bytes(options.limit || '100kb')
: options.limit
You can change the limit in your app.js by
var bodyParser = require('body-parser');
...
app.use(bodyParser.urlencoded( { limit: 1048576 } )); //1mb
I use this workaround...
HTML:
<input type="file" style="display:none" value="" id="uploadNewAttachment"/>
JavaScript:
In JavaScript you can upload files using the 3 method:
var binBlob = []; // If you use AngularJS, better leave it out of the DOM
var fi = document.getElementById('uploadNewAttachment');
fi.onchange = function(e) {
r = new FileReader();
r.onloadend = function(ev) {
binBlob[binBlob.length] = ev.target.result;
};
//r.readAsDataURL(e.target.files[0]); // Very slow due to Base64 encoding
//r.readAsBinaryString(e.target.files[0]); // Slow and may result in incompatible chars with AJAX and PHP side
r.readAsArrayBuffer(e.target.files[0]); // Fast and Furious!
};
$(fi).trigger('click');
What we have, javascript side is an Uint8Array of byte with values from 0 to 255 (or a Int8Array -128 to 127).
When this Array is sent via AJAX, it is "maximized" using signs and commas. This increases the number of total bytes sent.
EX:
[123, 38, 98, 240, 136, ...] or worse: [-123, 38, -81, 127, -127, ...]
As you can see, the number of characters transmitted is oversized.
We can instead proceed as follows:
Before send data over AJAX, do this:
var hexBlob = [];
for(var idx=0; idx<binBlob.length; idx++) {
var ex = Array.from(new Uint8Array(binBlob[idx]));;
for(var i=0;i<ex.length; i++) {
ex[i] = ex[i].toString(16).padStart(2,'0');
};
hexBlob[idx] = ex.join('');
}
What you have now, is a string of hex bytes in chars!
Ex:
3a05f4c9...
that use less chars of a signed or unsigned javascript array.
PHP:
On the PHP side, you can decode this array, directly to binary data, simply using:
for($idx=0; $idx<=count($hexBlob); $idx++) {
// ...
$binData = pack('H*',$hexBlob[$idx]);
$bytesWritten = file_put_contents($path.'/'.$fileName[$idx], $binData);
//...
}
This solution worked very well for me.
Avoid using the FormData API when Uploading Large Files1
The FormData API encodes data in base64 which add 33% extra overhead.
Instead of sending FormData, send the file directly:
app.service('fileUpload', function ($http) {
this.uploadFileToUrl = function (url, file) {
̶v̶a̶r̶ ̶f̶d̶ ̶=̶ ̶n̶e̶w̶ ̶F̶o̶r̶m̶D̶a̶t̶a̶(̶)̶;̶
̶f̶d̶.̶a̶p̶p̶e̶n̶d̶(̶'̶f̶i̶l̶e̶'̶,̶ ̶f̶i̶l̶e̶)̶;̶
̶r̶e̶t̶u̶r̶n̶ ̶$̶h̶t̶t̶p̶.̶p̶o̶s̶t̶(̶u̶r̶l̶,̶ ̶f̶d̶,̶ ̶{̶
return $http.post(url, file, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
});
};
});
When the browser sends FormData, it uses 'Content-Type': multipart/formdata and encodes each part using base64.
When the browser sends a file (or blob), it sets the content type to the MIME-type of the file (or blob). It puts the binary data in the body of the request.
How to enable <input type="file"> to work with ng-model2
Out of the box, the ng-model directive does not work with input type="file". It needs a directive:
app.directive("selectNgFile", function() {
return {
require: "ngModel",
link: function postLink(scope,elem,attrs,ngModel) {
elem.on("change", function(e) {
var files = elem[0].files[0];
ngModel.$setViewValue(files);
})
}
}
});
Usage:
<input type="file" select-ng-file ng-model="rsdCtrl.viewData.file" name="file"/>

AngularJS $http.delete not working in Azure App Service

A follow-up on a similar question I posted yesterday. I am trying to delete data from a table in Azure App service. This is my function in my Angular file.
function delName(user) {
//$scope.categories.push(user);
alert("about to delete. Action cannot be undone. Continue?")
$http.delete('https://test-evangelists-1.azurewebsites.net/tables/people', user, config)
.then(function (res) {
$scope.getNames();
});
}
Then I added an HTML button:
<button id="btn-del-evangelist" class="btn btn-default btn" ng-click="delName(user);">Delete User</button>
This is the value of my headers variable:
var config = {
headers: {
'Access-Control-Allow-Origin':'*',
'ZUMO-API-VERSION': '2.0.0'
}
};
But when I tried to run it, the console returns the following error:
which states that the header for ZUMO-API-VERSION must be specified.
Below is my code for GET and POST
GET:
function getNames() {
$http.get('https://test-evangelists-1.azurewebsites.net/tables/people', config)
.then(function (res) {
console.log(res);
$scope.people = res.data;
});
}
POST
function addName(user){
//$scope.categories.push(user);
alert("about to post!")
$http.post('https://test-evangelists-1.azurewebsites.net/tables/people', user, config)
.then(function (res) {
$scope.getNames();
});
}
Since I have already specified the header in my variable, I wonder what can be wrong here. Any help will be appreciated.
UPDATE:
I figured out that the Id must be appended to the URL before I can perform delete. However, I need to run a GET to retrieve the ID given the parameters but I am still encountering errors when getting the ID.
This is now my Delete function
function delName(user) {
alert("About to delete. Action cannot be undone. Continue?")
var retrievedId = "";
$http.get('https://test-evangelists-1.azurewebsites.net/tables/people', {
params: { name: user.name, location: user.location },
headers: { 'Access-Control-Allow-Origin': '*', 'ZUMO-API-VERSION': '2.0.0' }
})
.then(function (res) {
retrievedId = res.id;
alert(retrievedId);
});
$http.delete('https://test-evangelists-1.azurewebsites.net/tables/people/' + retrievedId, config)
.then(function (res) {
$scope.getNames();
});
}
Does anyone know what is wrong in the GET command when getting the ID?
UPDATE 2: I have written instead an Web Method (asmx) that will connect to SQL server to retrieve the ID passing the needed parameters. The ID will be returned as a string literal but in JSON format. Then I called JSON.parse to parse the string into JSON object then assigned the ID to a variable to which I appended in the URL. –
This is now my Delete function after I have written the Web Method.
function delName(user) {
var confirmres = confirm("You are about to delete this record. Action cannot be undone. Continue?");
var retrievedId = "";
if (confirmres == true) {
//get the ID via web service
$http.get('\\angular\\EvangelistsWebService.asmx/GetId', {
params: { name: user.name, location: user.location },
headers: { 'Access-Control-Allow-Origin': '*', 'ZUMO-API-VERSION': '2.0.0' },
dataType: "json",
contentType: "application/json; charset=utf-8"
})
.then(function (res) {
$scope.retData = res.data;
var obj = JSON.parse($scope.retData);
angular.forEach(obj, function (item) {
if (item.length == 0)
alert('No data found');
else {
//perform delete after getting the ID and append it to url
$http.delete('https://test-evangelists-1.azurewebsites.net/tables/people/' + item.id, config)
.then(function (res) {
$scope.getNames();
});
alert(item.id + ' deleted');
}
});
});
}
}
That is one way that I have learned on how to call HTTP DELETE on AngularJS. But I don't know if that is the optimal one. In any case, that works for me, unless there will be other suggestions.
$http.delete only has one parameter (config), not two (data, config).
Delete API
delete(url, [config]);
vs.
Post API
post(url, data, [config]);
To your updated problem:
To delete an item from your table, it appears the correct url is:
/tables/tablename/:id
Note the : before id.

"NetworkError: 404 Not Found" when i upload a file using AngularJS and Node.JS

I'm facing this error when I want to upload a file :
"NetworkError: 404 Not Found - http://127.0.0.1:8080/src/img"
404: Not found
This is the code of my page view:
<div class="col-sm-10">
<input type="file" ngf-select="uploadFiles($file)"
accept="image/*" ngf-max-height="1000" ngf-max-size="1MB">
</div>
I'm using ngFileUpload module
My controller :
$scope.uploadFiles = function(file){
if (file) {
file.upload = Upload.upload({
url: 'img',
data: {file: file}
});
file.upload.then(function (response) {
$timeout(function () {
console.log(response.data);
});
}, function (response) {
if (response.status > 0)
console.log(response.status + ': ' + response.data);
});
}
}
When I test the link: http://127.0.0.1:8080/src/img in my browser works fine but in my app doesn't work, and I get the above error.
What am I doing wrong?
You can use Multer npm module to upload files in Node.js. It would be very easy if you are using express for routing. It can be used as a middleware.
From their website:
var express = require('express')
var multer = require('multer')
var upload = multer({ dest: 'uploads/' })
var app = express()
app.post('/profile', upload.single('avatar'), function (req, res, next) {
// req.file is the `avatar` file
// req.body will hold the text fields, if there were any
})
app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
// req.files is array of `photos` files
// req.body will contain the text fields, if there were any
})
var cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])
app.post('/cool-profile', cpUpload, function (req, res, next) {
// req.files is an object (String -> Array) where fieldname is the key, and the value is array of files
//
// e.g.
// req.files['avatar'][0] -> File
// req.files['gallery'] -> Array
//
// req.body will contain the text fields, if there were any
})
The file will be saved in uploads directory with a long and wierd file name but you can access the original file name using req object, for example req.file.originalname for single file upload. You can then use fs.readFile and fs.writeFile to move that file to required location with acutall filename.

Using html2pdf with angularjs

Hey guys I'm trying to generate a pdf file using html2pdf but I couldn't succeed to make it work because I get an unreadable content
so basically what I have is a simple php page that generate a pdf file
$content = ob_get_clean();
require_once(dirname(__FILE__).'/../vendor/autoload.php');
try
{
$html2pdf = new HTML2PDF('P', 'A4', 'fr', true, 'UTF-8', 0);
$html2pdf->writeHTML($content, isset($_GET['vuehtml']));
$html2pdf->createIndex('Sommaire', 25, 12, false, true, 1);
$html2pdf->Output('bookmark.pdf');
}
catch(HTML2PDF_exception $e) {
echo $e;
exit;
}
from the other side I have my service that he sends some data to it and get the file back something like this
this.generatePDF = function (commande) {
var deferred = $q.defer();
$http({
method: 'POST',
//responseType: 'arraybuffer',
url: 'vendor/modules/html2pdf/examples/bookmark.php',
timeout: 15000,
data: $.param({'data': commande}),
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
//headers: {'Content-Type': 'application/pdf'}
//header :{"Content-Disposition": "attachment; filename=sample.pdf"}
}).then(function successCallback(response) {
debugger;
deferred.resolve(response.data);
}, function errorCallback(response) {
deferred.resolve(response.statusText);
});
return deferred.promise;
};
for the last part which is the controller side when the user presse generate I call my service and bind data to it and then get the whole stuff back after success and write it into the content of a new window
var popup = $window.open('', 'TEST', 'width=500,height=900');
ServiceCommande.generatePDF($scope.commande).then(function (data) {
popup.document.write(data);
});
the thing is a get some strange stuff instead of the pdf that I send
strange behavior pdf format
Thank you ^^
Try to use PhantomJS`. It has got a wide support for CSS elements.
Install it, and put the executable in system's environment PATH.
Create a file index.js. Contents of this file will be:
//create page
var page= require('webpage').create();
var system = require('system');
page.paperSize = {
format: 'A4',
orientation: 'portrait'
}
//check for number of parameters
if (system.args.length < 3) {
console.log('Usage: phantomjs index.js <web page URL> <ouptut path/filename>');
phantom.exit();
}
page.open(system.args[1], function (status) {
console.log("Status: " + status);
if (status === "success") {
page.render(system.args[2]);
}
else {
console.log("Failed")
}
phantom.exit();
});
Now fetch any webpage link, it will convert it into pdf, issuing commands as:
phantomjs index.js index.html index.pdf

Upload multipart form data with filename in Request Payload

I am still confused about different method of uploading files. The backend server is not under my control but I can upload a file using Swagger page or Postman. That means the server is functioning OK. But when I use AngularJS to do the upload, it doesn't work.
Here is what works using Postman to test. I am just using form-data:
Notice that Request Headers has Content-Type as multipart/form-data. But the Request Payload has filename and Content-Type as image/png.
Here is my code:
$http({
method: 'POST',
url: ApiUrlFull + 'Job/Item?smartTermId=0&name=aaa1&quantity=1&ApiKey=ABC',
headers: { 'Content-Type': undefined },
transformRequest: function(data) {
var fd = new FormData();
fd.append('file', params.imageData);
return fd;
}
})
params is just an object with file url in imageData.
My code also send similar URL params (so we can ignore that causing issues). But the Request Payload is base64 and it looks different as it is missing the filename field.
I have zero control of the backend and it is written in .NET.
So I guess my question is: Using Angular (either $http or $resource), how do I modify the request so that I am sending the correct Request Payload as how Postman does it? I cannot figure out how to reverse engineer this.
I have tried this https://github.com/danialfarid/ng-file-upload and it actually did OPTIONS request first before POST (assuming CORS issue). But the server gave 405 error for OPTIONS.
You can use something along the line of:
<input type="file" name="file" onchange="uploadFile(this.files)"/>
And in your code:
$scope.uploadFile = function(files) {
var fd = new FormData();
//Take the first selected file
fd.append("file", files[0]);
var uploadUrl = ApiUrlFull + 'Job/Item?smartTermId=0&name=aaa1&quantity=1&ApiKey=ABC';
$http.post(uploadUrl, fd, {
withCredentials: true,
headers: {'Content-Type': undefined },
transformRequest: angular.identity
}).success( ...all right!... ).error( ..damn!... );
};
My need was a follows.
In the form there is a default picture.
Clicking the picture opens a file select window.
When the user selects a file, it is uploaded right away to the server.
As soon as I get a response that the file is valid display the picture to the user instead of the default picture, and add a remove button next to it.
If the user clicks on an existing picture, the file select window reopens.
I tried to use a few code snippets on github that didn't solve the problem, but guided me in the right way, And what I ended up doing is as so:
Directive
angular.module("App").directive('fileModel', function ($parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.files = {};
var model = $parse(attrs.fileModel);
var modelSetter = model.assign;
// I wanted it to upload on select of file, and display to the user.
element.bind('change', function () {
scope.$apply(function () {
modelSetter(scope, element[0].files[0]);
});
// The function in the controller that uploads the file.
scope.uploadFile();
});
}
};
});
Html
<div class="form-group form-md-line-input">
<!-- A remove button after file has been selected -->
<span class="icon-close pull-right"
ng-if="settings.profile_picture"
ng-click="settings.profile_picture = null"></span>
<!-- Show the picture on the scope or a default picture -->
<label for="file-pic">
<img ng-src="{{ settings.profile_picture || DefaultPic }}"
class="clickable" width="100%">
</label>
<!-- The actual form field for the file -->
<input id="file-pic" type="file" file-model="files.pic" style="display: none;" />
</div>
Controller
$scope.DefaultPic = '/default.png';
$scope.uploadFile = function (event) {
var filename = 'myPic';
var file = $scope.files.pic;
var uploadUrl = "/fileUpload";
file('upfile.php', file, filename).then(function (newfile) {
$scope.settings.profile_picture = newfile.Results;
$scope.files = {};
});
};
function file(q, file, fileName) {
var fd = new FormData();
fd.append('fileToUpload', file);
fd.append('fn', fileName);
fd.append('submit', 'ok');
return $http.post(serviceBase + q, fd, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
}).then(function (results) {
return results.data;
});
}
Hope it helps.
P.S. A lot of code was striped from this example, if you need clarification just comment.

Resources