I'm trying to pick up angular.js and working on figuring out some of the things that are a bit less documented.
Consider this - I have a search method on the server that accepts query params and returns a collection of search results, and responds to GET /search.json route (Rails FWIW).
So with jQuery, a sample query would look like this:
$.getJSON('/search', { q: "javascript", limit: 10 }, function(resp) {
// resp is an array of objects: [ { ... }, { ... }, ... ]
});
I'm trying implement this using angular, and wrap my head around how it works. This is what I have now:
var app = angular.module('searchApp', ['ngResource']);
app.controller('SearchController', ['$scope', '$resource', function($scope, $resource){
$scope.search = function() {
var Search = $resource('/search.json');
Search.query({ q: "javascript", limit: 10 }, function(resp){
// I expected resp be the same as before, i.e
// an array of Resource objects: [ { ... }, { ... }, ... ]
});
}
}]);
And in the view:
<body ng-app="searchApp">
...
<div ng-controller="SearchController">
...
<form ng-submit="search()">...</form>
...
</div>
</body>
However, I keep getting errors like TypeError: Object #<Resource> has no method 'push' and $apply already in progress.
Things seem to work out as expected if I change the $resource initialization to the following:
var Search = $resource("/search.json?" + $.param({ q: "javascript", limit: 10 }));
Search.query(function(resp){ ... });
It seems more intuitive to initialize the $resource once and then pass different query parameters with changes in the requested search. I wonder if I'm doing it wrong (most likely) or just misunderstood the docs that calling $resource.query with the query params object as the first argument is feasible. thanks.
TypeError: Object # has no method 'push' and $apply already
in progress
because you have not defined a resources with the name Search. First you need to define such a resource. Doc: $resource. Here is an example implementation
angular.module('MyService', ['ngResource'])
.factory('MyResource', ['$resource', function($resource){
var MyResource = $resource('/api/:action/:query',{
query:'#query'
}, {
search: {
method: 'GET',
params: {
action: "search",
query: '#query'
}
}
});
return MyResource;
}]);
Include this module in you app and use it in a controller like this
$scope.search_results = MyResource.search({
query: 'foobar'
}, function(result){});
However I am not sure if this is what you need. The resource service interacts with RESTful server-side data sources aka REST API.
Maybe you need just a simple http get:
$http({method: 'GET', url: '/someUrl'}).
success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
http://docs.angularjs.org/api/ng.$http
Related
I've got a problem with service configuration. I want to display one user by this function:
$scope.findOne = function() {
$scope.user = Users.get({
userId: $stateParams.userId
});
};
But I am in trouble with User service :( I don't know, how should I change my the code to avoid angular error:
Error in resource configuration for action object. Expected response
to contain an array but got an {2}
Here is a code of my actual working service (without function findOne working of course:))
'use strict';
angular.module('users').factory('Users', ['$resource',
function($resource) {
return $resource('users', {}, {
update: {
method: 'PUT'
},
remove: {
method: 'DELETE',
url: 'users/:id',
params: {id: '#_id'}
}
});
}
]);
At a guess, I'd say your users API endpoint is expecting /users/:userId for GET requests. Your code at the moment will request /users?userId=nnn. You need to add an action for get with the ID in the URL, eg
return $resource('users', {id: '#userId'}, {
get: {
method: 'GET',
url: 'users/:id',
isArray: false
},
// etc
You can also make users/:id the default URL as long as it doesn't interfere with your other action configurations.
I'm trying to grab a single result from my expressjs api from within my AngularJS factory.
The factory looks like this and grabs all posts from my api(written in expressjs and getting data from mongodb), which is working fine:
angular.module('bonsaiService', ['ngResource']).
factory('bonsaiService', function($q,$resource) {
var bonsaiResource = $resource('http://localhost:8888/api/bonsais/:bonsaiId',{},{
get:{
method: 'GET',
params:{bonsaiId:''},
isArray: true
}
});
return {
get:function(){
var q = $q.defer();
bonsaiResource.get({
},
function(resp){
q.resolve(resp);
},function(httpResponse){
q.reject(httpResponse);
});
return q.promise;
}
//find by id
};
});
What i've tried so far is adding :bonsaiId after the $resource url and adding params for that id like this: params:{bonsaiId: ''}.
The server part (expressJS) look like this:
router.route('/bonsais/:bonsaiId')
.get(function(req,res){
Bonsai.findOne(req.params.bonsaiId,function(err,bonsai){
if(err)
res.send(err);
res.json(bonsai)
})
})
When I call a local url (with and existing _id from mongodb) it works fine and returns my data in json :
http://localhost:8888/api/bonsais/536be2e2ae54668818000001
Now in the controller im trying to get this data in my scope, which is not working.
bonsaiService.get({bonsaiId:$routeParams.bonsaiId}).then(
function(data){
$scope.trees = data;
console.log(data);
});
How do I make this work?
You could use a more simple approach here.
The query method for $resource already defines a GET on an array, which is QUERY.
Why not write your service this way :
.factory('bonsaiService', ['$resource',
function($resource) {
return $resource('http://localhost:8888/api/bonsais/:bonsaiId', {
bonsaiId: '#bonsaiId'
});
}
])
And in your controller, it would work like this :
bonsaiService.query({
bonsaiId: $routeParams.bonsaiId
}, function success() {
//Your code
}, function err() {
//Your code
});
Don't forget to inject the service in the controller or the app file, if it's not done already.
Hi I'am trying a simple example of using a controller and a factory to get some data back to the view but for some reason I can't print it.
I managed to get the ajax call to work.
If I type the
$scope.sampleStyles = [{ sample: 'text here', text : 'dasdas'}
and don't use the ajax call it works
UPDATE: if I add an alert before assigning to my scope it works (ajax has time to do his thing)
anyone know how to overcome that?
CODE:
var packageApp = angular.module("packageApp", []);
packageApp.controller("MyController", function($scope, myFactory){
$scope.sampleStyles = [];
function init(){
$scope.sampleStyles = myFactory.getSampleStyles();
}
init();
});
packageApp.factory('myFactory', function($http, $log){
var factory = {};
var sampleStyles = [];
var tempData = {};
factory.update = function(){
$.ajax({
type: 'POST',
url: '/account/fetch-sample-styles',
data: {
source: 'ajax'
},
success: function(data, textStatus, XMLHttpRequest){
tempData = data;
}
});
alert(tempData);
sampleStyles = tempData;
}
factory.getSampleStyles = function(){
factory.update();
return sampleStyles;
};
return factory;
});
Are you using the AngularJs $http service? If so it will return a promise which you then operate on. Here is more on promises from the AngularJs docs.
My guess is, you are using an ajax.get(...) with a success callback defined inside. The problem is probably due to the success callback not belonging to the "AngularJs world."
To fix this, you need to tell AngularJs that its scope has changed. Use the $[Root]scope.$apply() function, and have the scope injected into your service as a dependency.
Something like this inside the factory:
$.ajax({
url: "/api/some/end/:point",
...
success: function(data) {
$scope.$apply(function() {
$scope.sampleStyles = data; // etc
});
}
});
I strongly recommend that you look into the $http service, it makes the above code much nicer, and is designed to play nice with the $scope.
$http.get("/api/end/point").then(function(response) {
// response.data points at the page data sent back, assuming that your
// api endpoint sends back JSON of the likes of
// { status: "SUCCESS", styles: [...] }
$scope.sampleStyles = response.data.styles;
});
EDIT:
Now that you posted some code, it seems like the root of your issue is based on the fact that the ajax get is an async call. Why are you even messing with using a temporary variable? Why not the following?
factory.update = function(){
$.ajax({
type: 'POST',
url: '/account/fetch-sample-styles',
data: {
source: 'ajax'
},
success: function(data, textStatus, XMLHttpRequest){
sampleStyles = data;
}
});
}
If you really wanted to make the $.ajax call blocking, you can set async: false in the $.ajax properties.
EDIT 2:
Fixed some broken links, sorry I am a SO newb :(
I am having an issue with query parameters from my AngularJS app
I am reading documents from MongoDB using DreamFactory rest api like this:
.service('Servant', ['$resource', function($resource) {
// define and return $resource
return $resource('https://mydsp.cloud.dreamfactory.com:443/rest/mongodb/tablename',
{
// set params to bind too
app_name: 'myapp',
fields: '#fields',
limit: '#limit',
offset: '#offset',
filter: '#filter'
},
{
// set update method to 'PUT'
update: {
method: 'PUT'
}
}
)
}]);
This all works great when I set filter like "parameter=value" but I failed to find a way of passing more complicated filter param in JSON format as described here, using $in parameter etc. Does anyone know the right syntax for this?
EDIT:
just tried something like
filter = angular.toJson("{'parameter':{$in:['value1','value2']}}")
with no success...
First...drop the port from your service url. 'https' for dreamfactory specifies port 443. No need for you to do it explicitly. Second...You should be able to pass a SQL style filter as a string in your params. When you set up your $resource the way you have you should be able to pass a params object to it. No need to stringify or toJson anything. DreamFactory should handle it. For example...
Here is your service:
.service('Servant', ['$resource', function($resource) {
return $resource('https://mydsp.cloud.dreamfactory.com/rest/mongodb/tablename',
{
app_name: 'myapp',
fields: '#fields',
limit: '#limit',
offset: '#offset',
filter: '#filter'
},
{
update: {
method: 'PUT'
}
}
}]);
Calling that service with a params object:
// the 'parameter' value in our filter string should relate to a field and/or property
scope.paramsObj = {
fields: '*',
limit: 10,
offset: 0,
filter: 'parameter in (5,15)'
}
// call service and handle promise returned by $resource
Servant.get(scope.paramsObj).then(
function(result) {
// handle success
// like assign to a var or something
// here we just log it
console.log(result)
},
function(error) {
// handle error
// probably should throw an error here
// but we just log it here
console.log(error);
});
EDIT
Ok. So...it should work with SQL style filter strings. An issue has been logged with DreamFactory. In the mean time you can create a custom $resource action to handle the filters and tunnel your GET request through a POST. Easier then it sounds. See code below.
Here is the service with custom action
.service('Servant', ['DSP_URL', '$resource', function (DSP_URL, $resource) {
return $resource(DSP_URL + '/rest/mongohq/Colors', {
// params to bind to
app_name: YOUR_APP_NAME_HERE,
fields: '#fields',
limit: '#limit',
offset: '#offset'
}, {
// custom $resource action
'getFiltered': {
// set our method to post because we have to post
// our filter object
method: 'POST',
// We can transform the data before the post.
// In the circumstance we do need to stringify
// So that's what we do here.
transformRequest: function (data) {
return JSON.stringify(data);
}
}
})
}]);
Here is the controller:
.controller('MongoCtrl', ['$scope', 'Servant', function ($scope, Servant) {
// Create a params object
// This requests all fields.
// And we explicitly set the method to
// GET. We are tunneling a GET request
// through our POST because our filter
// needs to be posted but we really want a GET.
$scope.params = {
fields: '*',
method: 'GET'
};
// Call our Service with our custom $resource action
Servant.getFiltered(
// Send our params
$scope.params,
// Send our filter as post data
{
"filter": {
"color": {
"$in": ["blue", "white"]
}
}
},
// handle success
function (data) {
console.log(data)
},
// handle error
function (error) {
console.log(error)
})
}])
I guess you should stringify your filter data:
resource.update( {
filter: JSON.stringify( {qty:{$in:[5,15]}} )
});
Or in this way:
resource.get({id:123}, function() {
resource.filter = JSON.stringify( {qty:{$in:[5,15]}} );
resource.$update();
});
my service has to use a query string due to limitations on the server that runs classic ASP:
angular
.module('myServices', ['ng', 'ngResource'])
.factory('Item', ['$resource',
function ($resource) {
return $resource('/api/?p=item/:id');
}]);
and I want to add extra query string parameters to it:
Item.query({test: 123}, on_success, on_error);
but the resulting url is
/api/?p=item?test=123
apparently there is a bug, but how to get around it?
EDIT: filed this at https://github.com/angular/angular.js/issues/1511
You can use resource parameters. If you haven't specified placeholders in the path, they would automatically be converted into query string params. Like that:
angular
.module('myServices', ['ng', 'ngResource'])
.factory('Item', [
'$resource',
function ($resource) {
return $resource('/api');
}]);
Item.query({p: 'item/1'});
This would result in a request to /api?p=item/1.
P.S.
I suppose you already know that, but you don't like it. But I still think this is the correct way in your case. Considering the bad API design you are dealing with that back-end you could wrap the AngularJS resources with another service which does this for you.
var deferred = $q.defer();
api.api_name.query({
'param':param_value
},
function(response) {
deferred.resolve(response);
},
function(response) {
deferred.reject(response);
}
);
//
angular
.module('module_name')
.factory('api',function($resource){
var api_var={};
api_var.api_name = $resource('url?param_key=:param', {
param: '#param'
}, {
'query': {
method: 'get'
}
});
return api_var;
});