Angular service containing http get call not working with ng-repeat - http

I have an angular controller that calls a service. The service is responsible for returning data from a json file.
controller:
function projectController($scope, ajaxServices) {
$scope.projects = ajaxServices.getProjects();
}
service:
projectManagerApp.factory('ajaxServices', function ($http) {
return {
getProjects : function () {
$http.get('projects.json', { data: {} }).success(function (data) {
if (window.console && console.log) {
console.log("objects returned: " + data.length); // shows # of items
}
return data //nothing ng-repeated, no console errors.
})
// Exact same data from json file hard-coded, works fine
// when not commented out.
// return [{ "id": 1, "name": "Project 1 }, { "id": 2, "name": "Project 2" }]
}
}
});
html: ng-repeat="project in projects"
In the success function I can see the data returned in the console log but if I try to return the data the ng-repeat ul element on my page is empty. In the same service if I simply return the same data logged to the console hard coded (outside of the success function, of course it works just fine.
How can I return the data into the ng-repeat using my ajax call?
I'm just as new to Plunker as I am Angular but here is my attempt at a Plunk:
http://plnkr.co/edit/ALa9q6

$http is asynchronous, therefore the call to getProjects will return nothing. Using $q you can receive an instance to a promise which will receive the data when available.
Using $q
Here an example using $q:
http://plnkr.co/edit/U72oJblvrVEgYt2mTmU2?p=preview
Using $resource
Alternatively, you can use $resource especially if your server code is RESTful, which requires adding the following dependency in your scripts:
//ajax.googleapis.com/ajax/libs/angularjs/1.1.4/angular-resource.js
This is a 1st review of your code to use $resource: http://plnkr.co/edit/tLOAaXZHdGgWOok3Sdc8?p=preview
But you can simplify and shrink it more to this:
http://plnkr.co/edit/pKO6k6GxJ1RlO8SNvqUo?p=preview
This is the new app.js file:
angular.module('app', ['ngResource'])
.factory('ProjectsService', ['$resource', function($resource) {
return $resource('projects.json');
}])
.controller('ProjectsController', ['ProjectsService', '$scope', function(ProjectsService, $scope) {
$scope.projects = ProjectsService.query();
}]);
Find more information about $resource here:
http://docs.angularjs.org/api/ngResource.$resource

You need to use $q. Example is here

$http performs asynchronously and may or may not be finished at any given point in time which is why your return statement dont work.
Use $q or simply handle the promise inside the controller:
Service:
projectManagerApp.factory('ajaxServices', function ($http) {
return {
getProjects : function () {
return $http.get('projects.json', { data: {} })
}
}
});
Controller:
function projectController($scope, ajaxServices) {
ajaxServices.getProjects().success(function (data) {
if (window.console && console.log) {
console.log("objects returned: " + data.length);
}
$scope.projects = data
});
}
Plunk

If you want data to be loaded before the page
Is loaded you can use 'resolve' property
For the module.
Have a look in the docs for details.

Related

Data from db undefined at first, then changes to actual data

I am doing a find on a collection of nodes and am then storing the info in a variable.
I am then console.logging this data to see the contents of it. The thing is, my data stays an empty array for a split second and then a new console.log is done with an array of data. See my code below:
Template.temperature.helpers({
node() {
let node = Nodes.find({_id:Session.get('selectedNode')});
console.log(node);
return '';
}
});
My console output:
1: []
2: [Object]
What could be the reason for this?
In your router, subscribe within waitOn:
Router.route('/home', {
waitOn: function() {
return [
Meteor.subscribe('nodes'),
];
},
action: function() {
if (this.ready()) {
this.render();
}
},
});
This will ensure that the route will wait until the subscription is completed before executing the route. It uses the meteor loading hook, so the wait will utilize whatever loading screen or animation you ahve setup.

Working with Meteor and Async balanced-payments functions

I'm using balanced-payments and their version 1.1 of balanced.js within Meteor.
I'm trying to create a new customer using
balanced.marketplace.customers.create(formData);
Here is my CheckFormSubmitEvents.js file
Template.CheckFormSubmit.events({
'submit form': function (e, tmpl) {
e.preventDefault();
var recurringStatus = $(e.target).find('[name=is_recurring]').is(':checked');
var checkForm = {
name: $(e.target).find('[name=name]').val(),
account_number: $(e.target).find('[name=account_number]').val(),
routing_number: $(e.target).find('[name=routing_number]').val(),
recurring: { is_recurring: recurringStatus },
created_at: new Date
}
checkForm._id = Donations.insert(checkForm);
Meteor.call("balancedCardCreate", checkForm, function(error, result) {
console.log(result);
// Successful tokenization
if(result.status_code === 201 && result.href) {
// Send to your backend
jQuery.post(responseTarget, {
uri: result.href
}, function(r) {
// Check your backend result
if(r.status === 201) {
// Your successful logic here from backend
} else {
// Your failure logic here from backend
}
});
} else {
// Failed to tokenize, your error logic here
}
// Debuging, just displays the tokenization result in a pretty div
$('#response .panel-body pre').html(JSON.stringify(result, false, 4));
$('#response').slideDown(300);
});
}
});
Here is my Methods.js file
var wrappedDelayedFunction = Async.wrap(balanced.marketplace.customers.create);
Meteor.methods({
balancedCardCreate: function (formData) {
console.log(formData);
var response = wrappedDelayedFunction(formData);
console.log(response);
return response;
}
});
I get nothing back when I submit the form, except that on the server console I do see the log of the form data.
I'm sure I'm not calling some of these async functions correctly. The hard part for me here is that the balanced function are async, but I don't know if they fit into the same mold as some of the examples I've seen.
I've tried to follow this example code.
http://meteorhacks.com/improved-async-utilities-in-meteor-npm.html
Is there a specific change that needs to be done in regard to working with balanced here? Does anyone have any tips for working with Async functions or see something specific about my code that I've done wrong?
Thanks
The NPM utilities Async.wrap does the same thing as the undocumented Meteor function Meteor._wrapAsync, in that it takes an asynchronous function with the last argument function(err, result) {} and turns it into a synchronous function which takes the same arguments, but either returns a result or throws an error instead of using the callback. The function yields in a Fiber until the asynchronous callback returns, so that other code in the event loop can run.
One pitfall with this is that you need to make sure that the function you wrap is called with the correct context. So if balanced.marketplace.customers.create is a prototype method that expects this to be set to something, it will not be set properly unless you bind it yourself, using function.bind or any of the other various library polyfills.
For more information, see https://stackoverflow.com/a/21542356/586086.
What I ended up doing was using a future. This works great, I just need to do better at catching errors. Which will be a question for a pro I think ; - )
Credit should go to user3374348 for answering another similar question of mine, which solved both of these.
https://stackoverflow.com/a/23777507/582309
var Future = Npm.require("fibers/future");
function extractFromPromise(promise) {
var fut = new Future();
promise.then(function (result) {
fut["return"](result);
}, function (error) {
fut["throw"](error);
});
return fut.wait();
}
Meteor.methods({
createCustomer: function (data) {
balanced.configure(Meteor.settings.balancedPaymentsAPI);
var customerData = extractFromPromise(balanced.marketplace.customers.create({
'name': data.fname + " " + data.lname,
"address": {
"city": data.city,
"state": data.region,
"line1": data.address_line1,
"line2": data.address_line2,
"postal_code": data.postal_code,
},
'email': data.email_address,
'phone': data.phone_number
}));
var card = extractFromPromise(balanced.marketplace.cards.create({
'number': data.card_number,
'expiration_year': data.expiry_year,
'expiration_month': data.expiry_month,
'cvv': data.cvv
}));
var associate = extractFromPromise(card.associate_to_customer(customerData.href).debit({
"amount": data.total_amount*100,
"appears_on_statement_as": "Trash Mountain" }));
});
As Andrew mentioned, you need to set the context for the method.
Here's the way you can do that with Async.wrap
Async.wrap(balanced.marketplace.customers, "create");

Different URLs for Model and Collection in Titanium Alloy

Env: Titanium 3.1.3, Alloy 1.2.2.
I'm using the following adapter for persistence on the models/collections: https://github.com/viezel/napp.alloy.adapter.restapi
I have an API that has a different URL structure for a collection than it does a single model. Consider the following:
To get a single record: [GET] /files/:id
To get all the files for a user: [GET] /users/:id/files
I have the following schema for files.js:
exports.definition = {
config: {
"URL": "https://my.api.here/files",
//"debug": 1,
"adapter": {
"type": "restapi",
"collection_name": "files",
"idAttribute": "id"
}
},
extendModel: function(Model) {
_.extend(Model.prototype, {});
return Model;
},
extendCollection: function(Collection) {
_.extend(Collection.prototype, {
initialize: function(){
this.url = "http://my.api.here/users/"+this.user_id+"/files";
}
});
return Collection;
}
}
What I'm trying to do in the above is override the collection initialize method to change the URL structure for the collection. I then call this accordingly:
var currentUserFiles = Alloy.createCollection("files", {user_id:"12345"});
currentUserFiles.fetch({
success: function(files){
console.log("Woo! Got the user's files!");
console.log(JSON.stringify(files.models));
},
error: function(){
console.log("Nope");
}
});
This doesn't work. The fetch() method just continues to try to call /files. I've tried setting url as a property on the collection after it's created, that also don't work.
Ideally, I'd like to do this for both local instances as well as the singleton version of the collection.
So - the question is: can I utilize a different URL for a collection than I do for a model? Obviously, I don't want to just call /files and sort/filter client-side - that'd be a nightmare with a lot of records. What am I missing here?
It's a bit late but for anyone else that comes across this. I problem is where/how the url is specified for model and collection. The model needs a specific id (eg: primary key) passed into it because the model can only be one object. If you need more than one object, then use the collection. Hope this helps :)
extendModel : function(Model) {
_.extend(Model.prototype, {
url : function() {
return "http://my.api.here/users/"+this.user_id+"/files/"+ FILE_ID
},
});
return Model;
},
extendCollection : function(Collection) {
_.extend(Collection.prototype, {
url : function() {
return "http://my.api.here/users/"+this.user_id+"/files"
},
});
},

AngularJS - refresh view after http request, $rootScope.apply returns $digest already in progress

I am simply trying to load data when my app starts. However, the view loads faster than the http request(of course). I want to refresh my view once my data has been properly loaded because that data defines my view.
I've tried $rootScope.apply from inside the factory where I do my http request, and I also tried directly doing the http request in my controller again with $scope.apply, and neither one worked as they both gave me "$digest already in progress"
Any idea how can I set up my code to make my views refresh on data load? I will be having several different http requests and I would like to know how to set them up properly! I would really appreciate any input!
Here is some of the code I am working with.
app.factory('HttpRequestFactory', function($http, $q) {
var HttpRequestFactory = {
async: function(url, params) {
var deferred = $q.defer();
$http({
url: url,
method: post,
params: params
})
.success(function(data, status, headers, config) {
deferred.resolve(data);
})
.error(function(data, status, headers, config) {
deferred.reject("An error occurred");
});
return deferred.promise;
}
};
return HttpRequestFactory;
});
Factory
function initializeAll(){
HttpRequestFactory.async('../api', {action: 'getall'}).then(function(data) {
//$rootScope.$apply(function () {
allData = data;
//});
angular.forEach(allData, function(value, index){
console.log('Voala!');
});
});
}
Controller calling the factory's function initializeAll()
app.controller("MainController", ["$scope", "$rootScope","MyFactory",
function($scope, $rootScope, MyFactory){
MyFactory.initializeAll();
}
]);
Oh my !
You got the f** matter with AngularJS !
In fact you have to do a "safeApply" like that for example :
$rootScope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
In AngularJS you can only have one $apply or $digest loop at the same time.
For details on these loops look at the docs :
http://docs.angularjs.org/guide/concepts
It will explain what is the $apply loop and you'll understand a lot of things about the two-way-data-binding in AngularJS
Hope it helps.
Don't use $apply: use $watch.
Calling $apply is (almost) always the wrong thing to do. The only time you should ever be calling it is if you've triggered a change outside of an 'angular' method; here, since the trigger occurs in an angular $http request, you can't call $apply because it's already being done at that moment by the $http block. Instead, what you want to do is $watch.
Official Doc for $scope.$watch() here
This will let you watch an object and update whenever it changes. I assume that your view is based on allData and you want it to update immediately; if you're using an ng method, then the watch is automatically setup for you and no more work should be needed. If you're using allData yourself inside a controller, you can write the watch in that controller like this:
$scope.$watch(function thingYouWantToWatch(){
return <accessor call to allData here>;
},
function whatToDoOnChange(newValue, oldValue){
$scope.myCoolThing = newValue; //this is the newValue of allData, or whatever you're watching.
}
);

AngularJS service http success function using wrong "this" scope

The success function of a $http.put doesn't have access to the this scope of the service it's being called inside. I need to update a property of the service in the call back from the PUT request.
This is a cut down example of what I'm trying to do in a service:
var myApp = angular.module('myApp', function($routeProvider) {
// route provider stuff
}).service('CatalogueService', function($rootScope, $http) {
// create an array as part of my catalogue
this.items = [];
// make a call to get some data for the catalogue
this.add = function(id) {
$http.put(
$rootScope.apiURL,
{id:id}
).success(function(data,status,headers,config) {
// on success push the data to the catalogue
// when I try to access "this" - it treats it as the window
this.items.push(data);
}).success(function(data,status,headers,config) {
alert(data);
});
}
}
Sorry if there are some errors in the JS, the main point is how do I access the service scope from inside the success callback?
EDIT : while the answer to this question was correct, I switched to the factory method as both Josh and Mark recommended it
As far as I know, you can't. But I wouldn't try to run the service that way anyway. Here is a cleaner way:
.factory('CatalogueService', function($rootScope, $http) {
// We first define a private API for our service.
// Private vars.
var items = [];
// Private methods.
function add( id ) {
$http.put( $rootScope.apiURL, {id:id} )
.success(function(data,status,headers,config) { items.push(data); })
.then(function(response) { console.log(response.data); });
}
function store( obj ) {
// do stuff
}
function remove( obj ) {
// do stuff
}
// We now return a public API for our service.
return {
add: add,
store: store,
rm: remove
};
};
This is a very common pattern of developing services in AngularJS and it doesn't require any use of this in these cases.
Create a closure over a variable (often called that) that is assigned to this so that your callback functions will have access to your service object:
app.service('CatalogueService', function($rootScope, $http) {
var that = this;
...
).success(function(data,status,headers,config) {
that.items.push(data);
Here is a Plunker that uses $timeout instead of $http to demonstrate.

Resources