I have followed this example to post data from my Angular app to Node.js to post a webform to Sendgrid. This works fine after some changes and thanks a lot for the quickstart. Posting my form data to Sendgrid is working now!
For this project i'm using Angular Fullstack to be able to use Node functionalities within my Angular app.
However, this example has just input fields and a textarea. I want to be able to add a file (PDF, Docx, e.g) so that people can send an attachment to the recipient via Sendgrid. I have searched for a solution but couldn't find a working example. Maybe because it is not possible what i want to achieve.
MY VIEW (CLIENT):
<div ng-controller="ContactFormCtrl" id="contactformbox" style="margin-top:50px;" class="mainbox" >
<div class="panel panel-info" >
<div class="panel-heading">
<div class="panel-title">Solliciteer direct</div>
</div>
<div style="padding-top:30px" class="panel-body" >
<form id="loginform" class="form-horizontal" role="form" name="contactform">
<div style="margin-bottom: 25px" class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
<input type="email" name="to" ng-model="email.to" ng-required="true" id="email-to" class="form-control" name="username" value="" placeholder="The emailadres from the employer">
</div>
<div style="margin-bottom: 25px" class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span>
<input type="email" name="from" ng-model="email.from" ng-required="true" id="email-from" class="form-control" name="email-from" placeholder="Your e-mail address">
</div>
<div style="margin-bottom: 25px" class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span>
<input type="text" name="subject" ng-model="email.subject" ng-required="true" id="email-subject" class="form-control" name="subject" placeholder="Your subject please">
</div>
<div style="margin-bottom: 25px" class="input-group">
<input type="file" name="file" ng-model="email.file" ng-required="true" id="email-file" class="form-control" name="file">
</div>
<div style="margin-bottom: 25px" class="input-group">
<textarea ng-model="email.text" name="text" placeholder="Enter Text Here.." class="form-control" rows="5" id="comment"></textarea>
</div>
<div style="margin-top:10px" class="form-group">
<!-- Button -->
<div class="col-sm-12 controls">
<button id="emailSubmitBn" class="btn btn-success" type="submit" ng-click="submitEmail()">Submit</button>
</div>
</div>
</form>
</div>
</div>
MY CONTROLLER (CLIENT):
angular.module('angularMyApp')
.controller('ContactFormCtrl', function ($scope, $http) {
$scope.submitEmail = function() {
console.log("TEST");
//Request
$http.post('/api/email', $scope.email)
.success(function(data, status) {
console.log("Sent ok");
})
.error(function(data, status) {
console.log("Error");
})
};
});
MY APP.JS (SERVER):
'use strict';
// Set default node environment to development
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var express = require('express');
var config = require('./config/environment');
var http = require('http');
var bodyParser = require('body-parser');
var options = {
auth: {
api_key: process.env.SENDGRID_APIKEY;
}
}
var nodemailer = require('nodemailer');
var sgTransport = require('nodemailer-sendgrid-transport');
// Setup server
var app = express();
var server = require('http').createServer(app);
require('./config/express')(app);
require('./routes')(app);
var mailer = nodemailer.createTransport(sgTransport(options));
app.post('/api/email', function(req, res) {
var mailOptions = {
to: ['[email protected]', req.body.to],
from: req.body.from,
subject: req.body.subject,
text: req.body.text
};
mailer.sendMail(mailOptions, function(err, res) {
if (err) {
console.log(err)
}
console.log(res);
});
});
// Start server
server.listen(config.port, config.ip, function () {
console.log('Express server listening on %d, in %s mode', config.port, app.get('env'));
});
// Expose app
exports = module.exports = app;
Well there are two main issues:
Many thanks in advance!
The first trouble you have is that ng-model
doesn't work with <input type="file" />
. So, you will need to create a custom directive to populate the model with the file.
.directive('fileModel', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var model = $parse(attrs.fileModel);
var modelSetter = model.assign;
element.bind('change', function(){
scope.$apply(function(){
modelSetter(scope, element[0].files[0]);
});
});
}
};
}]);
Then use the directive on your file input element like this:
<input type="file" file-model="email.attachment" ng-required="true" id="email-attachment" name="attachment" class="form-control" />
Note that I changed email.file
to email.attachment
to avoid confusion in the rest of the code.
Next, you need to include the file in your AJAX request. You will need to use FormData
for that. Populate it from the scope using FormData.append()
:
$scope.submitEmail = function() {
var formData = new FormData();
Object.keys($scope.email).forEach(function(key) {
formData.append(key, $scope.email[key]);
});
$http.post('/api/email', formData, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
}).then(function(data, status) {
console.log("Sent ok");
}, function(data, status) {
console.log("Error");
});
};
Notice I also passed a config object to $http.post()
. This is to prevent angular from parsing the FormData
object and setting the content type.
I relied heavily on this blog post for this part of the answer.
To access the file in express, use multer.
Install:
$ npm install --save multer
Usage:
var multer = require('multer');
var upload = multer();
app.post('/api/email', upload.single('attachment'), function(req, res) {
// req.file contains the 'attachment' file
...
});
From Nodemailer's Readme section on email fields it says to use an attachments
property that is an array of attachment
objects.
app.post('/api/email', upload.single('attachment'), function(req, res) {
var mailOptions = {
to: ['[email protected]', req.body.to],
from: req.body.from,
subject: req.body.subject,
text: req.body.text,
attachments: [
{
filename: req.file.originalname,
content: req.file.buffer
}
]
};
mailer.sendMail(mailOptions, function(err, res) {
if (err) {
console.log(err)
}
console.log(res);
});
});
The above example keeps the attachment in memory, which could be bad if large files are uploaded frequently. You can write files to disk instead:
var upload = multer({ dest: '/uploads' });
Then, instead of setting the file's buffer
as the attachment's content
, you would set the attachment's path
to the file's path
:
attachments: [
{
filename: req.file.originalname,
path: req.file.path
}
]
I finally got the job done thanks to gilly3. I had to do one minor change in his code. In the controller I changed:
formData.set(key, $scope.email[key]);
to the following code:
formData.append(key, $scope.email[key]);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With