I have a basic CRUD app, my main problem is with the delete function.
The angular controller calls the $http action
'use strict';
(function(){
var app = angular.module('JournalApp', [])
.controller('JournalController', function($scope, $http){
var loadEntries = function(){
$http.get('/api/entry')
.then(function(response){
$scope.entries = response.data;
if(response.data.noEntries){
$scope.entries.noEntries = true;
};
});
};
loadEntries();
//Create new Journal Entry
$scope.addEntry = function(entry){
var data = ({
hours: entry.hours,
notes: entry.notes
});
$http.post('/api/entry', data);
loadEntries();
};
//Delete Journal Entry
$scope.delEntry = function(item){
$http({
method: 'DELETE',
url: '/api/entry',
// data: item,
data: {date: item.date},
headers: {'Content-Type': 'application/json;charset=utf8'}
}, loadEntries());
};
});
})();
which is routed to my server side handler
//GET Request
this.getEntries = function(req, res){
journal.find().sort({date: -1}).toArray(function(err, doc){
if(err) throw err;
if(doc.length){
console.log('found entries');
res.json(doc);
} else {
console.log('Returning no Entries.')
res.json(({ "noEntries": true }));
}
})
};
//Post Request
this.addEntry = function(req, res){
console.log('Adding new entry...');
var time = new Date();
var notation = req.body.notes || 'n/a'
journal.insert({ hours: req.body.hours, notes: notation, date: new Date()})
};
//Delete Request
this.deleteEntry = function(req, res){
journal.findOne({"date": new Date(req.body.date)}, function(err, data){
if(err) throw err;
console.log('Removing item _id: ' + data._id);
journal.remove({"date": data.date});
});
};
}
module.exports = entryHandler;
I can verify through mongo (and the console.log) that the entry was deleted and everything works as its supposed to. However the $http callback does not fire. The callback function (loadEntries) im using to refresh the journal entires on the page.
I can get the callback to fire when I write the code out as
$http.delete('/api/entry', item).then( function(){ loadEntries() } );
However when I use this method the server side handler doesnt work properly.
Is there a better way to go about this without a ton of extra modules? I'd preffer to learn the basics before I start adding packages all over the place doing the work for me.
EDIT:
I've tried adding .then(function(){ loadEntries() }); to my angular code, and it does not work.
You need to call .then on your $http call to execute the callback function:
$http({
method: 'DELETE',
url: '/api/entry',
// data: item,
data: {date: item.date},
headers: {'Content-Type': 'application/json;charset=utf8'}
}).then(function() {
loadEntries()
});
I was unable to successfully call my callback no matter what I tried. However my goal was to remove div's from my app, so I just used the angular ng-show/hide directive to accomplish this.
Apart from .then(), It looks like server code is not returning any call completion status such as success/failure similar to - Ok/BadRequest (IHTTPctionResult type) in C#
Related
I work on 2 connected web apps in NodeJS. Let's call them A and B. Most of it works, but...
In app A there is a video stream and the user can ask a question in the form. I use jquery and ajax to submit the form and send a post request, that inserts a question into MongoDB - each as a separate document. App B reads the questions from the collection using jquery and ajax as well. It makes a get request every 5 seconds - I used setInterval. And here we go.
When I start both apps they work for some time, BUT randomly app A sends a post request with a question added previously. Sometimes app B erases all documents from DB, etc. I guess there is logic behind this, but I can't see it.
Below are the problematic pieces of code.
App A
API part:
index.post('/ask', (req, res) => {
var question = req.body.question;
var person = req.body.person;
mongo.connect(url, {useNewUrlParser:true}, (err, dbo) => {
if(err) return;
else{
var db = dbo.db('question');
db.collection('asked').insertOne({question:question, who:person}, (err) => {
if(err) return;
})
}
})
})
ajax part:
$('#ask_question').on('submit', function(event) {
event.preventDefault();
event.stopImmediatePropagation();
var question = $('#inputDefault').val();
var who = $('#exampleSelect1').val();
console.log(question + ' '+ who);
$.ajax({
url: '/ask',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
question: question,
person:who
}),
success: function(response) {
console.log(response);
}
});
});
App B
API:
index.get('/asked', (req, res) => {
mongo.connect(url, {useNewUrlParser:true}, (err, dbo) => {
if(err) return;
else{
var db = dbo.db('question');
db.collection('asked').find({}).toArray((err, result) => {
if(err) return;
if(!result) return;
else res.send(result);
})
}
})
})
ajax part:
setInterval(()=>{
$.ajax({
url: '/asked',
method:'GET',
contentType: 'application/json',
async: false,
success: function(response) {
var form = $('#asked');
$('#asked').empty();
$.each(response, function(i, question) {
form.append('<li>'+question.question+ ' ('+question.who+')</li>');
});
}
});
},5000);
If you're wondering why I use return it's because I'm trying to prevent MongoDB from crashing. After some time (like 15 minutes or so) I get an error:
.../node_modules/mongodb/lib/operations/mongo_client_ops.js:474
throw err;
^
Error: write EPIPE
at Object.exports._errnoException (util.js:870:11)
at exports._exceptionWithHostPort (util.js:893:20)
at WriteWrap.afterWrite (net.js:763:14)
It happens in both apps. I guess it's a simple deadlock, but don't know how to prevent this.
If you want I'll provide a bigger part of the project. I can't figure out solutions for these problems.
I want to call a function in the same controller but I mostly have an error like: "ReferenceError: myFunctionB is not defined"
module.exports = {
myfunctionA: function(req, res){
var theword;
theword = myFunctionB({id:26})
return res.json({say:theword})
},
myfunctionB: function(req, res){
var id = req.param('id');
niceword.get({
id:id
}, function(err, word){
if(err){return res.negotiate(err);}
if(!word){return res.json({err:"no word found"})}
return res.json({word:word});
})
}
}
I also tryed by to put myFunctionB into a service but, as I use many other controller etc I have no response.. Any idea?
There are a few ways to do this. The proper way, as yBrodsky says, is to create a service that runs a callback or returns a promise:
myServiceFunctionB: function(params, cb) {
var id = params.id;
niceword.get({
id:id
}, function(err, word){
return cb({ say: { word: word });
});
}
And then in your controller, just use:
return MyServiceName.myServiceFunctionB({id: 26}, res.json);
You can also pass in your req and res to continue using those:
myServiceFunctionB: function(req, res) { ...
Alternatively, you can use the this keyword in your controller:
myfunctionA: function(req, res){
req.params.id = 26;
return this.myfunctionB(req, res);
}
If you'll be doing more complicated logic where context gets lost, just set a new var at the start using this:
myfunctionA: function(req, res){
var self = this;
req.params.id = 26;
//...lots of nested promises or callbacks...
return self.myfunctionB(req, res);
}
Thank you yBrodsky and wwwslinge, I finaly get what I need thank of you.
I had to make a little change because I still need to use the data after they pass into the function.
Controller
MyServiceName.functionB({id:1}, function(data){
// doing some stuff with the data and then
return res.json({data: data});
});
Service
functionB: function(params, cb){
var id = params.id;
Niceword.getbyid({
id:id
}, function(err, word){
if(err){return err}
return cb({word:word});
})
}
I'm building a twitter sentiment reader and I am running into issues when I try and send a response back to angular. Everything worked fine before I added res.send() and it logged the sentiment.
Now that I added the res.send() function, it errors out sending the data back to angular. Angular has it as an error 500 and my node console has the error saying POST /postUsername Security Handshake Failed: some library stuff Description: End of TCP Stream".
Express Route
router.post("/postUsername", function(req, res){
console.log(req.body.userName);
var client = new Twitter({
consumer_key: '',
consumer_secret: '',
access_token_key: '',
access_token_secret: ''
});// client
client.get('statuses/user_timeline', {screen_name: req.body.userName, count:20}, function(error, tweets, response) {
tweet = analyzeIt.seperateData(tweets);
var document = language.document(tweet);
if (!error) {
var parsedScore;
var parsedMagnitude;
var finalScore;
var options = {
verbose: true
}; // options
document.detectSentiment(options, function(err, score) {
if (err) {
console.log(err);
} //if err
parsedScore = score.score;
parsedMagnitude = score.magnitude;
finalScore = analyzeIt.finalScore(parsedScore, parsedMagnitude);
console.log(finalScore);
});//detect sentiment
}//if not error
});//client get statuses
res.send(finalScore);
});//router post
Angular Controller
app.controller('myController', function($scope, $http){
$scope.sendData = function() {
var data = { userName: $scope.userName };
console.log($scope.userName);
$http.post('/postUsername', data)
.success(function (data, status, headers, config) {
$scope.PostDataResponse = data;
})
.error(function (data, status, header, config) {
$scope.PostDataResponse = "Data: " + status;
});
};
});
The expected output would be something like "This user trends positive."
Any help would be appreciated!
I see a few problems. First is that you are responding immediately, without waiting for the twitter request to complete.
// Call order: #1
client.get(..., function(error, tweets, response) {
// Call order: #3
// anything in here no longer matters
});
// Call order: #2
res.send(finalScore) //because this executes before the code in the callback above
So essentially when the call is made from angular, express immediately sends back the value of finalScore which is undefined.
The other problem is you aren't really handing the error case. If there is an error with the twitter client, you should respond to the request in a meaningful way, rather than just logging to the console. This way you can see, inside angular what the problem is instead of having to scratch your head and look at your server console:
if(!error) {
//do normal stuff
}
else {
res.status(500).send("Twitter error: " + error);
}
Same goes for detectSentiment:
document.detectSentiment(options, function(err, score) {
if (err) {
console.log(err);
res.status(500).send("Error detecting sentiment: " +err);
}
});
So, to fix your issue, you need to be responding inside your callbacks, not after:
router.post("/postUsername", function(req, res){
...
client.get('statuses/user_timeline', {screen_name: req.body.userName, count:20}, function(error, tweets, response) {
...
if (!error) {
...
document.detectSentiment(options, function(err, score) {
if (err) {
console.log(err);
res.status(500).send("Error detecting sentiment: " + err);
} //if err
...
console.log(finalScore);
res.send(finalScore);
});//detect sentiment
}
else {
res.status(500).send("Twitter error: " + error);
}
});//client get statuses
});//router post
It seems a little weird, at first, that you have to nest your response so deep, but it's not at all. This is the world of javascript. There are ways to use promises and deferred objects to clean up your code, but for now it's best to write it like this so that it really sinks in how asynchronous code in javascript works.
I'm using Express and trying to teach myself node/javascript callbacks and I've stumbled across something.
I have a route that looks like this:
var express = require('express');
var router = express.Router();
var api = require('../api');
router.get('/',function(req, res, next){
var modulename = api.modulename;
modulename.methodname(res);
});
module.exports = router;
And then the module that is being called above looks like this:
var library = require('library');
var instances = {};
var modulename = {
getAllInstances: function(res) {
var request = new library.asyncMethod();
request.on('success', function(resp) {
instances = resp.data;
res.setHeader("Content-Type","application/json");
var returnInstances = {
id: instances[0].InstanceId,
state: {name: instances[0].State.Name, code: instances[0].State.Code}
};
res.send(returnInstances);
})
.on('error', function(resp){
console.log(resp);
})
}
};
module.exports = modulename;
As you can see I'm passing through the response parameter through to my module, but I'd rather pass back instances and then in the route return api.modulename.instances, like this:
var library = require('library');
var instances = {};
var modulename = {
getAllInstances: function() {
var request = new library.asyncMethod();
request.on('success', function(resp) {
var returnData = resp.data;
instances = {
id: returnData[0].InstanceId,
state: {name: returnData[0].State.Name, code: returnData[0].State.Code}
};
})
.on('error', function(resp){
console.log(resp);
})
.send();
}
};
module.exports = modulename;
However, when I do, it's coming through as the default value {} but if I run it as above, I do get output so I know that there should be data in there.
Let me know if I have misunderstood your issue. If you are saying you want to pass back objects from getAllInstances then you pass in a callback and call it from the event handler like this-
router.get('/',function(req, res, next){
var modulename = api.modulename;
modulename.getAllInstances(res, function(err, instances){
if(err){ ... }
else{
res.send(instances); //or however you want to use instances
}
});
});
and in getInstances
var modulename = {
getAllInstances: function(res, cb) {
var request = new library.asyncMethod();
request.on('success', function(resp) {
instances = resp.data;
var returnInstances = {
id: instances[0].InstanceId,
state: {name: instances[0].State.Name, code: instances[0].State.Code}
};
cb(null, instances);
})
.on('error', function(err){
cb(err, null));
});
//.send(); not sure what this is it seems to be request.send() ??
}
};
The problem here lies with when the response from the API call is available. The event loop in Node means code won't block until the API replies with a response. Hence a callback is needed to handle that response when it becomes available. You probably want to use the API response in your Express router response so there's a chain of dependency.
One strategy here would be to use promises and not callbacks, it would alleviate some of the pain you're experiencing with async response from the API call.
In your routes:
router.get('/',function(req, res, next){
var instances = [];
// The function below could be refactored into a library to minimise controller code.
var resolver = function (response) {
var data = JSON.parse(response);
instances.push({
name: data[0].State.Name,
code: data[0].State.Code
});
res.render('instances'. {instances : instances});
};
modulename.methodname(resolver);
});
And in your module:
var rp = require('request-promise'); // Also see q-io/http as an alternate lib.
var modulename = {
methodname: function (resolver) {
rp('http://the-inter.net')
.then(resolver)
.catch(console.error);
}
};
This might not cut-n-paste work but have a look at the request-promise examples for further clarification.
{ text: undefined,
done: false,
_id: 529e16025f5222dc36000002,
__v: 0 }
PUT /api/todos/529e16025f5222dc36000002 200 142ms - 68b
I keep getting this error when trying to do an update for my simple CRUD todo list. When I submit the update, the change doesn't appear on screen, although the put says it's a 200. Not sure what steps to take so that I don't get this "undefined" error and so I can have the update show up on screen.
EDIT: Included more code
This is the back-end node code:
app.put('/api/todos/:_id', function(req, res) {
Todo.findById(req.params._id, function(err, todos){
todos.text = req.body.text;
console.log(todos);
todos.save(function() {
if (!err) {
res.send(todos);
} else if (err) {
res.send(err);
}
Todo.find(function(err, todos) {
if (err)
res.send(err);
res.json(todos);
});
});
});
});
This is the Angular front-end code:
$scope.updateTodo = function(id) {
$scope.newItem = prompt("Please enter your new item:", "");
$http.put('/api/todos/' + id, {formData: $scope.newItem}).success(function(data) {
$scope.todos = data;
});
$http.get('/api/todos').success(function(data) {
$scope.todos = data;
});
};
I think it's because of this:
$http.put('/api/todos/' + id, { formData: $scope.newItem} )
^^^^^^^^
You're passing a single formData parameter with the request, yet in your Express code, you use this:
req.body.text
Either try this:
req.body.formData.text
Or don't use the formData parameter at all and pass $scope.newItem directly.
Besides that, your Express code is a bit messy: it might send back multiple responses and it doesn't check for errors on the save (as #PaulGray also pointed out).