Dynamic URL queries with AngularJS and NodeJS, MongoDB - angularjs

I really do not understand how to handle URLs with queries appended to it.
I have endpoints that accept several parameters like:
?max_length=50,
?min_length=1,
?active=true,
?only_today=true,
?etc...
Via AngularJS how can I set those value dynamically only if the user has checked for those values?
Actually I'm just building an object {} appending those parameters when the $scope is not null. But I don't think it is a good idea.
Same for NodeJS and MongoDB...
How can I get the correct object based on the query string on the URL?
What I'm doing here as well is to split up the URL and checking for the words, etc... I'm sure there is a better way and I can not find it in both documentations and wondering to bigger and bigger URL parameters it start to be hell.
I know this is a real low level question but I don't really understand how to handle it.
Thanks

You can use the $location service for that.
https://docs.angularjs.org/api/ng/service/$location

You can use $resource to easily map your endPoints to your services. You should map your params to what is expected in your api. And if you have conditional parameters, you need to handle undefined params in your backend and ignore these params. For mapping your endpoints in nodeJS check out Restify
For example:
angular.module("myApp", []).factory("myFactory", function($resource) {
var YourResource = $resource('/rest/yourResource');
var factory = {
retriveMyResources: function(paramsQuery) {
return YourResource.query(paramsQuery).$promise;
}
};
return factory;
}).controller("myCtrl", function($scope, myFactory) {
myFactory.retrieveMyResources({
maxLength: $scope.maxLength,
sortBy: $scope.sortBy
}).then(function(result) {
$scope.result = result
})
})
Your node server
//App.js you initialize your server, and include your route files
(function() {
var restify = require("restify");
var server = restify.createServer({
name: "test-server"
});
server.pre(restify.CORS());
server.use(restify.gzipResponse());
server.use(restify.acceptParser(server.acceptable));
server.use(restify.queryParser());
server.use(restify.bodyParser());
server.use(restify.jsonp());
require("./routes/your_resource_route.js")(server);
server.listen("1921", function() {
console.log('%s listening at %s environment: %s', server.name, server.url, process.env.NODE_ENV);
});
})();
Example Route file
module.exports = function(server) {
var yourResourceService = require("services/your_resource_service.js");
server.get("rest/yourResource",
function(req, res, next) {
return yourResourceService.findResources(req.params.maxLength, req.params.sortBy).then(function(resources) {
res.send(200, resources);
next();
}).catch(function(err) {
res.send(500, err);
next();
}).done();
}
);
}
And your service file
module.exports = function(app_context) {
var exampleService = {
findItems: function(maxLength, sortBy) {
var sortObject = {};
sortObject[sortBy || DEFAULT_SORT] = -1;
return Q(brandMongooseCollection.find({}).sort(sortObject).limit(maxLength || DEFAULT_MAX_LENGTH).lean().exec());
}
};
return exampleService;
};

Related

Unable to create POST request to REST API with $resource in angularjs

I am learning about the MEAN stack, and have created a REST API which posts a review to a collection in MongoDB.
I have defined a service as given:
angular.module('myApp')
.constant('baseURL', 'http://localhost:8080/');
angular.module('myApp')
.service('addReviews', ['$resource', 'baseURL', function($resource, baseURL) {
this.getReviews = function() {
return $resource(baseURL+'reviews/', null, {'save': {method: 'POST'}});
};
}]);
Now, I am calling this service from my controller:
angular.module('myApp', ['ngResource'])
.controller('reviewController', ['$scope', 'addReviews', function($scope, addReviews) {
$scope.reviewSubmit = function() {
$scope.receivedReviews = false;
var review = {
// some data
};
$scope.reviews = addReviews.getReviews().query(
function(response) {
$scope.reviews = response;
$scope.receivedReviews = true;
},
function(response) {
$scope.reviews = response;
// print error message
}
);
console.log($scope.reviews); // showing empty array
};
}]);
In routes.js, I have configured my route as:
var Reviews = require('./models/reviews');
...
app.post('/reviews', function(req, res) {
Reviews.create(req.body, function(err, post) {
if (err) {
return res.send(err);
}
return res.json(post);
});
});
I am trying to post a new review to the Reviews collection. However, $scope.reviews is showing an empty array. I logged the requests, and it shows a GET request is being to /reviews instead of POST. I think I should use save() instead of query(), but I have seen some tutorials online where they used query() despite the method being PUT/POST in the service. I am really confused. Can anyone point out how I can post the data (in var review) to the Reviews collection?
There are some issues with your code on the angular side of things.
You want to use $resource as an all-purpose object to communicate with the API. It has built-in functionality to:
query: get all resources from a given API endpoint
get: a single resource, usually by specifying that resource's id
save: post, with an object sent across in the body of the request. NOTE: you don't need the {'save': {method: 'POST'}} in your $resource configuration, you get it for free.
remove and delete: self-explanatory
So you'd want to set up your reviews factory (incl. url constant) like:
angular.module('myApp', ['ngResource'])
.constant('baseURL', 'http://localhost:8080/')
.factory('Reviews', ['$resource', 'baseURL', function($resource, baseURL) {
return $resource(baseURL+'reviews/:id', {id: '#id'});
}]);
If you want to have access to all saved reviews in your controller, as $scope.reviews, you'd do something like:
angular.module('myApp')
.controller('reviewController', ['$scope', 'Reviews', function($scope, Reviews) {
// hit API endpoint to get all reviews
// will have to have app.get('/reviews', function(req, res) {...})
// configured in your node code
Reviews.query(function(data) {
$scope.reviews = data;
}, function(error) {
console.log(error);
});
// and if you want to take a user-written review, say $scope.userReview,
// from the view and save it to the database on click function submitReview()...
$scope.userReview = {
message: '',
createdTime: null
};
// ^ not sure what your ReviewSchema looks like on the backend, but for example...
$scope.submitReview = function() {
if ($scope.userReview.message.length) {
$scope.userReview.createdTime = Date.now();
Reviews.save($scope.userReview);
// ^ this will make POST request with the $scope.userReview object as the request body
}
};
}]);
The create method on your back end looks fine. The object (or maybe just string) you send across will have to match your review schema. You may want to log the request body to make sure you're getting what you expect.
Have a look at this short post on using $resource to interact with RESTful APIs, and (the slightly more confusing) angular $resource docs, for more information on the $resource service.
Hope this helps you!

How to pass multiple parameters in a URL Angular/REST API

I'm trying to pass multiple parameters in a URL with no luck. I'm not sure if it makes a difference but I am doing it through Angular. I'm trying to send the request to a REST API backend that I know works for single requests. Here is what my backend looks like
index.js
var express = require('express');
var router = express.Router();
var game = require('./game');
router.get('/api/v1/gameRefined/:from_datepicker:to_datepicker:from_timepicker:to_timepicker:selectLevel', game.getAllRefined);
module.exports = router;
game.js
...dbconnection stuff...
var game={
getAllRefined: function(req, res) {
var refines = req.params;
console.log("getting refined games");
pool.getConnection(function(err, connection){
var query = connection.query('SELECT * FROM game WHERE date >= ? AND date <= ? AND time >= ? AND time <= ? and level = ?', [refines.from_datepicker, refines.to_datepicker, refines.from_timepicker, refines.to_timepicker, refines.selectLevel], function(err, rows) {
connection.release();
if(err) {
throw err;
}else{
res.json(rows);
}
});
})
},
}
module.exports = game;
I send the request from this factory
.factory('gameFactory', ['$http',
function($http) {
var _gameFactory = {};
_gameFactory.getRefinedGames = function(dateFrom,dateTo,timeFrom,timeTo,level) {
var encodedParam = encodeURIComponent(baseUrl + '/api/v1/gameRefined/?from_datepicker='+dateFrom+'&to_datepicker='+dateTo+'&from_timepicker='+timeFrom+'&to_timepicker='+timeTo+'&selectLevel='+level+'');
return $http.get(encodedParam);
}
return _gameFactory;
}])
That sends this request that comes back as a 404:
http://localhost:8100/http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fv1%2FgameRefined%2F%3Ffrom_datepicker%3D2015-02-05%26to_datepicker%3D2015-02-19%26from_timepicker%3D12%3A00%26to_timepicker%3D18%3A00%26selectLevel%3D1
I have tried it encoded and not encoded, with forward slashs, with semi colons, but nothing has worked so far.
I don't know why a localhost gets appended at the start, but even trying it in postman without the first localhost still is a 404 error.
How should I be doing this? Any help is appreciated. Thank you.
First, other than separating the work into a factory, you aren't really using Angular here. Second, I would consider posting to your API, return JSON results from the API, and then use a promise to read the data back into Angular.
Use $http.post
Let Angular do this work for you. Something like the below will generate the URL request for you. The return value of $http is also a promise, so using .success and .error will allow you to parse any returned data as well, even if it is just a success or failure message - but it is a great method of passing data between server/API and client.
.factory('gameFactory', ['$http',
function($http) {
return {
reachAPI: function(dateFrom, dateTo) {
$http.post('http://localhost:8080/api/v1', {
'dateFrom': dateFrom,
'dateTo': dateTo
})
.success(function(data, status, headers, config) {
*things happen*
data.somethingReturned // from your API
});
}
}]);
Consider body-parser
I know you said you have confidence in your REST API structure, but body-parser is an Express middleware that can parse URL-encoded strings and may prove helpful in reading your data. Personally, I think it lends towards more readable code.
var app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
router.route('/api').post(function(req, res) {
*things happen*
req.body.dateFrom //find your data
*things happen*
res.json(returnedData);
});
Hope that helps

Angular JS global config for resource success query

I have implemented resources in my single page angular app which fires to my REST client server. I have made different services for each resource. Now my REST server is sending a value in response header, now I want to know a proper way where I can retrieve that value from headers.
My service code:
app.service('$job', function($resource) {
var job = $resource(service_base_url+'jobs.json/:id');
return job;
});
My controller which is getting headers:
app.controllerProvider.register('JobPostsController',['$scope','$job', function($scope, $job) {
$scope.jobs = {};
$scope.job_titles = {};
$job.query(function(jobs,responseHeaders){
var headers = responseHeaders();
some_function(headers.user);
$scope.jobs = jobs.jobs;
});
}
]);
I am getting headers in my above code, but I don't want to inject it in all controllers. So is there a proper way to do it? Some single config code which will run for all future resources request or some kind of event which can be only triggered when successful resource response with 200 OK
Try interceptor.
I don't know exactly your logic. You could register a global interceptor which intercepts all requests:
angular.module('App', [])
.config(function ($httpProvider){
$httpProvider.interceptors.push(function() {
return {
'response': function(response) {
var headers = response.headers();
some_function(headers.user);
return response;
}
};
});
});
or just register an interceptor which runs only for all requests of this query.
app.service('$job', function($resource) {
var job = $resource(service_base_url+'jobs.json/:id',{}, {
'query': {
method:'GET',
isArray:true,
interceptor: {
'response': function(response) {
var headers = response.headers();
some_function(headers.user);
return response;
}
}
}
});
return job;
});
Side notes:
Should not use $ prefix for your service name as it's reserved for angular, it may conflict with angular future versions.
I guess you need .factory instead of .service
You can set up the service in a run block like:
angular.module('myApp', [])
.run(['$rootScope', '$job',function ($rootScope, $job) {
$rootScope.jobs = {};
$rootScope.job_titles = {};
$job.query(function(jobs,responseHeaders){
var headers = responseHeaders();
some_function(headers.user);
$rootScope.jobs = jobs.jobs;
});
}]);
The only drawback your service is global to the app
I would go with a base service factory. This would allow you to have common service related functionality in one place
app.factory('ServiceBase', function () {
function ServiceBase() {
this.responseHeaders = function responseHeaders(resp){
// todo
};
}
return ServiceBase;
});
app.service('$job', function($resource, ServiceBase) {
var service = function () {
// $job related functions here
};
angular.extend(service, new ServiceBase());
return service;
});
Now anything in the ServiceBase is accessible to the controller and to the service. This allows you to have common functionality, has no new injection dependencies (on the controller), and is easy to extend further.
I think angulrjs response interceptors can help u for this.

I am using MEAN.IO stack, why can't I hit my data-access layer using require?

So, I am using mean.io and for some reason, my routes.js never hits my 'index.all' method, or the 'exports.all' function, even though I require the functions from the server-side controller. Also, my routing is done using angular-ui-router. Does anybody know how to call a backend method from routing in MEAN.IO? I keep using:
'use strict';
module.exports = function(System, app, auth, database) {
// Home route
var index = require('../controllers/index');
app.route('/test').get(index.all);
app.route('/')
.get(index.render);
};
I would like to hit 'index.all' but even if I navigate to /test, it still gets
index.render. Does anybody know why?
Here is the controllers file:
'use strict';
var mean = require('meanio');
var mongoose = require('mongoose');
var Composition = mongoose.model('Composition');
exports.render = function(req, res) {
console.log(req.user);
var modules = [];
// Preparing angular modules list with dependencies
for (var name in mean.modules) {
modules.push({
name: name,
module: 'mean.' + name,
angularDependencies: mean.modules[name].angularDependencies
});
}
function isAdmin() {
return req.user && req.user.roles.indexOf('admin') !== -1;
}
// Send some basic starting info to the view
res.render('index', {
user: req.user ? {
name: req.user.name,
_id: req.user._id,
username: req.user.username,
roles: req.user.roles
} : {},
modules: modules,
isAdmin: isAdmin,
adminEnabled: isAdmin() && mean.moduleEnabled('mean-admin')
});
};
exports.all = function(req, res) {
console.log(req.user);
Composition.find({user: req.user}, 'title description').sort('-created').populate('user', 'name username').exec(function(err, compositions) {
if (err) {
return res.jsonp(500, {
error: 'Cannot list the compositions'
});
}
res.jsonp(compositions);
});
};
Is this a front-end or backend problem? Thanks for any advice that might be helpful.
You are navigating. So are you hitting the link in the browser url? Then you should try localhost:3000/test instead of localhost:3000/#!/test.
The urls of the form localhost:3000:/#!/<something> are angular routes. Look up angular routing and views. It is better to use angular views than server side rendering. Do angular routing for test and add a view corresponding to it. Fetch the dynamic data in the view using the regular $http.get calls.
Check this tutorial for routing and adding views in angular

Set RestAngular default request parameters after login

I'm hitting an API which requires all authenticated actions to include an auth token in the request, however, I do not have the auth token until I login.
I've only seen examples of setting default request parameters in Restangular in app.config.
Is it possible to set this until after the user has logged in and User.auth_token is set?
So basically instead of:
app.config(function(RestangularProvider) {
RestangularProvider.setDefaultRequestParams({
auth_token: 'thisistheauthenticationtoken'
});
});
I need:
app.config(function(RestangularProvider) {
RestangularProvider.setDefaultRequestParams({
auth_token: User.auth_token
});
});
Why would you set token as part of the response versus in the header? Like so.
Restangular.setDefaultHeaders({ authentication: 'bearer ' + token.authentication });
I know this is an old thread but this SO question kept appearing when I was Googling (yes, I just used Google as a verb... deal with it :P) for a resolution, so I thought I should provide my solution. Hopefully it will help the OP or anyone else that may come across this page.
angular.module("app").factory("UserService", [
"$rootScope",
"$state",
"$q",
"Restangular",
function ($rootScope, $state, $q, Restangular) {
var UserSvc = {};
var Identity;
/*
This creates a scoped copy of Restangular
Normally this is where you would use setDefaultRequestParams,
but it would only affect this scope and not ALL API requests in your app
*/
var UsersAPI = Restangular.withConfig(function (RestangularConfigurer) {
RestangularConfigurer.setBaseUrl("api/1.0/users");
});
UserSvc.login = function (credentials) {
var $defer = $q.defer();
UsersAPI.all("start-session").post(credentials).then(function(respData){
if (respData.apikey) {
Identity = respData.plain();
/*
User is authenticated and API key is obtained from server response
Note how I do NOT use the setDefaultRequestParams function:
If we do the withConfig/setDefaultRequestParams, it only affects local scope, not global
This method modifies the ROOT Restangular object and
will then propegate through all future use of Restangular in your app
*/
Restangular.configuration.defaultRequestParams.common.apikey = Identity.apikey;
if ($rootScope.toState && $rootScope.toState.name != "login") {
$state.go($rootScope.toState.name, $rootScope.toStateParams || {});
} else {
$state.go("app.dashboard");
}
$defer.resolve(Identity);
}
else {
Identity = undefined;
$defer.reject(Identity);
}
},function (respData) {
$defer.reject(respData);
});
return $defer.promise;
};
return UserSvc;
}
]);
In my case, I use
Restangular.setDefaultRequestParams({token: localstorage.get('token')});
This works with me. Please have a look my snippet here.
https://github.com/fugokidi/ng-snippets/blob/master/rest.js
If you want to do something like this, you need to remove your code from app.cofig and move to when you find user is logged in.
You can set defaultRestParams for restangular at any point of application using Restangular service.
For more info refer https://github.com/mgonto/restangular#setdefaultrequestparams.
A more Angular-ish example from a project that I've been working on:
angular.module('app', [ 'restangular' ])
.factory('API', function(Restangular){
return Restangular.withConfig(function(config){
config
.setBaseUrl('https://api.example.com')
// etc etc etc
; // END config
});
})
.factory('Auth', function(API){
return {
login: function(credentials){
// Assuming I just POST /session/new to get an OAuth token,
// which is totally not a thing that OAuth should do.
API.one('session').post('new', credentials)
.then(function(auth){ // Assuming `auth = { access_token: '...' }`
API.setDefaultHeaders({
Authorization: 'bearer ' + auth.access_token
// Assuming OAuth Bearer Token
});
})
},
logout: function(){ /* . . . */ }
};
})
.controller('MainController', function(API, Auth){
var self = this;
self.user = { };
this.login = function(credentials){
Auth.login(credentials).then(function(){
self.user = API.one('user').$object;
});
});
})
; // END module(app)
The following code will read the token from storage for every request.
app.config(function(RestangularProvider) {
//Injext $cookies manually (there might be better ways to do this)
var $cookies;
angular.injector(['ngCookies']).invoke(['$cookies', function(_$cookies_) {
$cookies = _$cookies_;
}]);
RestangularProvider.setDefaultHeaders({
Authorization: function() {
return $cookies.get('token');
}
});
});
I too struggled with this.
Instead of using
RestangularProvider.setDefaultRequestParams({
auth_token: 'thisistheauthenticationtoken'
});
try using
Restangular.setDefaultRequestParams({auth_token:'thisistheauthenticationtoken'});

Resources