Data for factory coming too late–Angular.js - javascript

I had similar code to this working before and for some reason, this one chunk is not. It's a simple idea; call the function, it calls an $http-based factory to get data and return an array of that data. First is the factory:
app.factory('getSensors', ['data', function (data) {
return { get : function() {
data.get('sensors').then(function (results) {
var sensors = [];
for(var i = 0; i < results.sensors.length; i++) {
sensors.push({
'id' : Number(results.sensors[i][0]),
'name' : results.sensors[i][1],
'type' : results.sensors[i][2],
'device' : results.sensors[i][3],
'unit' : results.sensors[i][4],
'parent' : Number(results.sensors[i][5]),
'latitude' : Number(results.sensors[i][6]),
'longitude' : Number(results.sensors[i][7])
});
}
return sensors;
});
}
};
}]);
Which uses the following data service:
app.factory('data', ['$http', function ($http) {
var serviceBase = '/api/';
var obj = {};
obj.get = function (q) {
return $http.get(serviceBase + q).then(function (results) {
return results.data;
});
};
return obj;
}]);
And for the call to the factory, I've tried getSensors, getSensors.get, and getSensors.get(), even though I know some of those are wrong. I have also tried not wrapping this code in a function, using a function defined above the return, and returned the array in no object (how it is working in my other function, even though I don't think it is best practice, it's just simpler). I've even swapped the for loop for a forEach.
Basically, the returned value from the getSensors factory always happens before the $http call completes from my use of console debugging, even if the code is in the callback function and after that loop. I am at a loss as to how this is possible after spending 2 hours looking at it and rewriting this code. I maybe am simply missing something obvious or am violating some aspect of Angular.

The critical part you are missing is to return the promise created by the .then() in your getSensors.get() method:
app.factory('getSensors', ['data', function (data) {
return { get : function() {
return data.get('sensors').then(function (results) {
var sensors = [];
/* for loop (same as before) goes here */
return sensors;
});
}
};
}]);
Then to use it, just inject the getSensors service as a dependency, and call
getSensors.get().then(function (sensors) {
/* do something with sensors array */
});

Related

Accessing values of promises in an array

I'm trying to create an array from http requests that holds a name property and two promises: one as an array and the other as an object. I'm able to get the info I need using this approach but I'm not able to access it to display it in the scope of the html. For instance, when I log out the array, "people" I get an array of objects (looking like: [Object, Object, Object]) and I have to expand a bunch of things to see the actual values of each object so that "person.skills" would really have to be "person.skills.$$state.value". Also, on the page, {{person.name}} will show up but the other two are just empty objects that look like this: {}. So how can I access the values of the promises so that I can just use {{person.skills}} to show the array?
js
var getPeople = function() {
var named = $q.defer();
$http.get('/getnames').success(function (response) {
named.resolve(response);
});
return named.promise;
};
getPeople().then(function(namesRes) {
var people = [];
names = namesRes;
names.forEach(function(index){
var name = index;
var count = $q.defer();
var skills = $q.defer();
var urls = '/getskillsbyname/' + name;
var urlc = '/getcountbyname/' + name;
$http.get(urls).success(function (response) {
skills.resolve(response);
});
$http.get(urlc).success(function (response) {
count.resolve(response);
});
people.push({name:name, skills:skills.promise, count:count.promise});
});
return people;
}).then(function(people) {
console.log(people);
$scope.people = people;
});
html
<div ng-repeat="person in people">
<p>{{person.name}}</p>
<p>{{person.skills}}</p>
<p>{{person.count}}</p>
</div>
Your method not returning promise correctly, you need to use $q for waiting till all the inner promises get completed.
I have implemented your code by maintaining grand promise variable in the forEach loop, whenever asking skills and couts call are made, it put that call inside $q.all and $q.all promise is moved to grandPromiseArray.
var getPeople = function() {
return $http.get('/getnames');
};
getPeople().then(function(response) {
var people = [];
names = response.data;
grandPromiseArray = [];
names.forEach(function(index) {
var name = index, count = $q.defer(), skills = [],
urls = '/getskillsbyname/' + name, urlc = '/getcountbyname/' + name;
grandPromiseArray.push(
$q.all([$http.get(urls), $http.get(urlc)])
.then(function(response) {
people.push({
name: name,
skills: response[0].data, //response[0] value returned by 1st promise
count: response[1].data //response[1] value returned by 2nd promise
});
})
);
});
return $q.all(grandPromiseArray).then(function() {
return people
});
})
.then(function(people) {
console.log(people);
$scope.people = people;
});
Example for a valid promise chain:
function getSomeData(){
var defer = $q.defer();
$http.get(urls).success(function (response) {
defer.resolve(response);
});
return defer.promise;
}
And then you can access the promise data like:
getSomeData().then(function(data){
console.log(data); // This is the response of the $http request
});
Organize your requests in functions and it looks much cleaner.
$http also returns promise by default, but I recommend to create a service/factory, which doing your requests, then you need the defer.
$http.get(urls).then(function(data){
});
A Promise isn't going to automatically allow you to display your results in Angular.
A Promise is an object, which lets you chain async operations together, by passing in functions which are eventually fired and passed the value.
You can't ask for myPromise.value and expect it to be there, because it's an async process which might take 20ms, 2mins, or might just never come back at all.
In terms of how this is structured, it might be much cleaner and easier to reason about if you broke the data-fetching parts out into a service, and just injected the service into the controller.
I'm not sure which version of Angular you've been working with, but I'm hoping its at least 1.2.
Also, my example is using the native Promise constructor, but I'm sure Angular's version of $q now has everything that's needed, if you aren't using a promise polyfill.
function PersonService (http) {
function getResponseData (response) { return response.data; }
function getURL (url) { return http.get(url).then(getResponseData); }
function makePerson (personData) {
return {
name: personData[0],
skills: personData[1],
count: personData[2]
};
}
var personService = {
getNames: function () { return getURL("/names/"); },
getSkills: function (name) { return getURL("/getskillsbyname/" + name); },
getCounts: function (name) { return getURL("/getcountsbyname/" + name); },
loadPerson: function (name) {
return Promise.all([
Promise.resolve(name),
personService.getSkills(name),
personService.getCount(name)
]).then(makePerson);
},
loadPeople: function (names) {
return Promise.all(names.map(personService.loadPerson));
}
};
return personService;
}
Here's what my person service might look like.
I've written a couple of super-small helper functions that keep getting reused.
Then I've made the service all about getting either people or details about people.
I'm using Promise.all, and passing it an array of promises. When every promise in the array is complete, it returns an array of all of the data returned by the promises. Promise.resolve is used in one spot. It basically returns a promise which succeeds automatically, with the value it was given. That makes it really useful, when you need a promise to start a chain, but you don't have to do anything special, aside from returning a value you already have.
My assumption is both that q now names its methods the same way the spec does, and that Angular's implementation of $q follows the spec as well.
function MyController (personService) {
var controller = this;
controller.people = [];
controller.error = false;
init();
function setPeople (people) {
controller.people = people || [];
}
function handleError (err) {
setPeople([]);
controller.error = true;
}
function init () {
return personService.getNames()
.then(personService.loadPeople)
.then(setPeople)
.catch(handleError);
}
}
My controller now gets really, really simple. It's using the service to get names, and load people, and then it sets them, and it's ready to go. If there was an error, I handle it in whatever way makes sense.
Handling the injection of this stuff is pretty quick and easy:
angular.module("myExample")
.service("PersonService", ["$http", PersonService])
.controller("MyController", ["PersonService", MyController]);
Using this on the page is now painless, as well:
<div ng-controller="MyController as widget">
<ul ng-hide="widget.people.length == 0">
<li ng-repeat="person in widget.people">
<person-details person="person"></person-details>
</li>
</ul>
<div ng-show="widget.error">Sorry, there was an error with your search.</div>
</div>
I think you could probably use $q.all to construct your people:
var p = $q.all({
name: $q.when(name),
skills: $http.get(urls),
count: $http.get(urlc)
});
p.then(function(person) {
people.push(person);
});
$q.all will construct a new promise that gets resolved when all of the input promises resolve. Since we input a map, $q.all will resolve with a map that has the same keys. The values are the resolutions of the corresponding promises. In this case, that's a person so we can then just push it into the people array directly.
This is a slightly naive implementation. Since the calls are asynchronous, there is no guarantee that the order of names will be preserved in people -- but it shouldn't be too hard for you to fix that if it is important.
Create an array of all the promises for the counts and skills. Have broken those requests up into their own functions for readability
Also then() of $http returns a promise object that has a property data
var getPeople = function() {
return $http.get('/getnames').then(function(response) {
return response.data;
});
};
getPeople().then(function(people) {
var promises = [];
people.forEach(function(person) {
// push new requests to promise array
promises.push(getSkills(person));
promises.push(getCount(person))
});
// when all promises resolved return `people`
return $q.all(promises).then(function() {
// return `people` to next `then()`
return people;
});
}).then(function(people) {
console.log(people);
$scope.people = people;
}).catch(function(){
// do something if anything gets rejected
});
function getCount(person) {
var urlc = '/getcountbyname/' + person.name;
return $http.get(urlc).then(function(response) {
person.count = response.data
});
}
function getSkills(person) {
var urls = '/getskillsbyname/' + person.name;
return $http.get(urls).then(function(response) {
person.skills = response.data
});
}

Using http requests, promises, ng-options, and factories or services together

I'm trying to retrieve a list of options from our database and I'm trying to use angular to do it. I've never used services before but I know that's going to be the best way to accomplish what I want if I'm going to use data from my object in other controllers on the page.
I followed a couple tutorials and put together a factory that makes an http request and returns the data. I've tried several ways of doing it, but for some reason nothing is happening. It's like it never runs the factory function and I can't figure out why.
Factory:
resortModule= angular.module('resortApp',[]);
resortModule.factory('locaService',['$http', function ($http){
var locaService= {};
locaService.locations = {};
var resorts = {};
locaService.getLocations=
function() {
$http.get('/url/url/dest/').success(function (data) {
locaService.locations = data;
});
return locaService.locations;
};
return locaService;
//This is a function I would like to run in addition to the first one so multiple variables would be stored and accessible
/*getResorts:
function(destination) {
$http.get('/url/url/dest/' + destination.id).success(function (data) {
resorts = data;
});
return resorts;
}*/
}]);
resortModule.controller('queryController',['$scope', 'locaService', function($scope, locaService) {
$scope.checkConditional= function (){
if($("#location").val() == ""){
$("#location").css('border','2px solid #EC7C22');
}
};
$scope.selectCheck= function (){
$("#location").css('border','2px solid #ffffff');
$(".conditional-check").hide();
};
$scope.resort;
$scope.locations= locaService.getLocations();
}]);
I just want the data to be returned and then assigned to the $scope.locations to be used for ng-options in the view. Then I want my other function to run on click for the next field to be populated by the variable resort. How would I do this? Any help would be great! Thanks!
$http service returns a promise, and your function should return that promise. Basically your getLocations function should be something like the following
locaService.getLocations=
function() {
return $http.get('/url/url/dest/');
};
Then in your controller you should retrieve the options using this promise:
locaService.getLocations()
.then(
function(locations) // $http returned a successful result
{$scope.locations = locations;}
,function(err){console.log(err)} // incase $http created an error, log the returned error);
Using jquery in controllers or manipulating dom elements in controllers is not a good practice, you can apply styles and css classes directly in views using ng-style or ng-class.
Here is an example how all it should look wired up:
resortModule= angular.module('resortApp',[]);
resortModule.factory('locaService',['$http', function ($http){
var locaService= {
locations: {}
};
var resorts = {};
locaService.getLocations= function() {
return $http.get('/url/url/dest/');
};
return locaService;
//This is a function I would like to run in addition to the first one so multiple variables would be stored and accessible
/*getResorts:
function(destination) {
$http.get('/url/url/dest/' + destination.id).success(function (data) {
resorts = data;
});
return resorts;
}*/
}]);
resortModule.controller('queryController',['$scope', 'locaService', function($scope, locaService) {
/* Apply these styles in html using ng-style
$scope.checkConditional= function (){
if($("#location").val() == ""){
$("#location").css('border','2px solid #EC7C22');
}
};
$scope.selectCheck= function (){
$("#location").css('border','2px solid #ffffff');
$(".conditional-check").hide();
};
*/
$scope.resort;
locaService.getLocations()
.then(
function(locations) // $http returned a successful result
{$scope.locations = locations;}
,function(err){console.log(err)} // incase $http created an error, log the returned error);
}]);

Factory has two methods, one is $http method with $promise - how to reference the other method from within the first one's success function?

In one of my factories I need to set a variable when data is fetched (through $http) so I can access it in my controller (the intention is to display a spinner image until the data is loaded).
.factory('LoadData', function LoadData($http, $q){
return {
getMyData: function(){
return $http(
// implementation of the call
).then(
function(response){
var myData = response.data;
// this should be reference to the other method (getLoadStatus)
// where I want to change its value to "true"
// this doesn't work - "this" or "self" don't work either because we're in another function
LoadData.getLoadStatus.status = true;
}
);
},
// by calling the below method from my controller,
// I want to mark the completion of data fetching
getLoadStatus = function(){
var status = null;
return status;
}
}
}
I hope you got the idea - how could this be accomplished? Also, I'm open to any suggestions which are aimed towards a better approach (I want to pick up best practice whenever possible).
Status is essentially a private variable; use it as:
.factory('LoadData', function LoadData($http, $q){
var status = null; // ESSENTIALLY PRIVATE TO THE SERVICE
return {
getMyData: function(){
return $http(...).then(function(response){
...
status = true;
});
},
getLoadStatus = function(){
return status;
}
};
})
There are several ways.
Here's one which I prefer to use:
.factory('LoadData', function LoadData($http, $q){
var status = false;
var service = {
getMyData: getMyData,
status: status
};
return service;
function getMyData() {
return $http(
// implementation of the call
).then(
function(response){
var myData = response.data;
status = true;
}
);
}
}
This provides good encapsulation of your methods and gives you a clean interface to export. No need for the getter method if you don't want it.
Inspiration via John Papa's Angular style guide (found here).
You could simply store variable flag in closure:
.factory('LoadData', function LoadData($http, $q) {
var status = false;
return {
getMyData: function() {
status = false;
return $http(/* implementation of the call */).then(function(response) {
status = true;
return response.data;
});
},
getLoadStatus: function() {
return status;
}
}
});
Also if getMyData loads fresh data every time, it's important to reset status to false before each request.
I actually decide to use a promise in my controller after calling this service and when data is returned, I am simply making the status true. It seems that is best practice to have a promise returned when calling a service so I should be good.

Can't pass around returned object from server

I am picking up a project started by a peer, and having trouble with an API call.
This is my angular controller:
angular.module('oowli').factory('Note', ['$http', function($http) {
var Note = function(data) {
angular.extend(this, data);
};
Note.prototype.save = function() {
var note = this;
return $http.post('/notes', this).then(function(response) {
note = response.data;
}, function(error) {
return error;
});
};
return Note;
}]);
and the call is executed in this function:
var saveNote = function(Note, scope){
return function(span, phrase){
var noteObj = new Note({
user: scope.global.user._id,
content: span.innerText
});
noteObj.save().then(function(){
console.log(noteObj);
});
};
};
problem is, after saving the note, noteObj is the original, not the one that comes back from the server (with an _id field).
Debugging on the Note.prototype.save, response.data comes with the _id;
I need to know how to have access to the returned Note object, in the saveNote function.
You are assigning the new object to the local variable 'note' in the Note.prototype.save function, but then not doing anything with it.
The quickest solution is to copy the properties of the returned object to your note rather than assign to it, so instead of:
note = response.data;
This might work:
angular.copy(response.data, note)
In general though I don't like the approach of having the Note class responsible for saving itself. I would create a note service that saves the object.

AngularJS : Asynchronously initialize filter

I'm having trouble trying to initialize a filter with asynchronous data.
The filter is very simple, it needs to translate paths to name, but to do so it needs a correspondance array, which I need to fetch from the server.
I could do things in the filter definition, before returning the function, but the asynchronous aspect prevents that
angular.module('angularApp').
filter('pathToName', function(Service){
// Do some things here
return function(input){
return input+'!'
}
}
Using a promise may be viable but I don't have any clear understanding on how angular loads filters.
This post explains how to achieve such magic with services, but is it possible to do the same for filters?
And if anyone has a better idea on how to translate those paths, I'm all ears.
EDIT:
I tried with the promise approch, but something isn't right, and I fail to see what:
angular.module('angularApp').filter('pathToName', function($q, Service){
var deferred = $q.defer();
var promise = deferred.promise;
Service.getCorresp().then(function(success){
deferred.resolve(success.data);
}, function(error){
deferred.reject();
});
return function(input){
return promise.then(
function(corresp){
if(corresp.hasOwnProperty(input))
return corresp[input];
else
return input;
}
)
};
});
I'm not really familliar with promises, is it the right way to use them?
Here is an example:
app.filter("testf", function($timeout) {
var data = null, // DATA RECEIVED ASYNCHRONOUSLY AND CACHED HERE
serviceInvoked = false;
function realFilter(value) { // REAL FILTER LOGIC
return ...;
}
return function(value) { // FILTER WRAPPER TO COPE WITH ASYNCHRONICITY
if( data === null ) {
if( !serviceInvoked ) {
serviceInvoked = true;
// CALL THE SERVICE THAT FETCHES THE DATA HERE
callService.then(function(result) {
data = result;
});
}
return "-"; // PLACEHOLDER WHILE LOADING, COULD BE EMPTY
}
else return realFilter(value);
}
});
This fiddle is a demonstration using timeouts instead of services.
EDIT: As per the comment of sgimeno, extra care must be taken for not calling the service more than once. See the serviceInvoked changes in the code above and the fiddles. See also forked fiddle with Angular 1.2.1 and a button to change the value and trigger digest cycles: forked fiddle
EDIT 2: As per the comment of Miha Eržen, this solution does no logner work for Angular 1.3. The solution is almost trivial though, using the $stateful filter flag, documented here under "Stateful filters", and the necessary forked fiddle.
Do note that this solution would hurt performance, as the filter is called each digest cycle. The performance degradation could be negligible or not, depending on the specific case.
Let's start with understanding why the original code doesn't work. I've simplified the original question a bit to make it more clear:
angular.module('angularApp').filter('pathToName', function(Service) {
return function(input) {
return Service.getCorresp().then(function(response) {
return response;
});
});
}
Basically, the filter calls an async function that returns the promise, then returns its value. A filter in angular expects you to return a value that can be easily printed, e.g string or number. However, in this case, even though it seems like we're returning the response of getCorresp, we are actually returning a new promise - The return value of any then() or catch() function is a promise.
Angular is trying to convert a promise object to a string via casting, getting nothing sensible in return and displays an empty string.
So what we need to do is, return a temporary string value and change it asynchroniously, like so:
JSFiddle
HTML:
<div ng-app="app" ng-controller="TestCtrl">
<div>{{'WelcomeTo' | translate}}</div>
<div>{{'GoodBye' | translate}}</div>
</div>
Javascript:
app.filter("translate", function($timeout, translationService) {
var isWaiting = false;
var translations = null;
function myFilter(input) {
var translationValue = "Loading...";
if(translations)
{
translationValue = translations[input];
} else {
if(isWaiting === false) {
isWaiting = true;
translationService.getTranslation(input).then(function(translationData) {
console.log("GetTranslation done");
translations = translationData;
isWaiting = false;
});
}
}
return translationValue;
};
return myFilter;
});
Everytime Angular tries to execute the filter, it would check if the translations were fetched already and if they weren't, it would return the "Loading..." value. We also use the isWaiting value to prevent calling the service more than once.
The example above works fine for Angular 1.2, however, among the changes in Angular 1.3, there is a performance improvement that changes the behavior of filters. Previously the filter function was called every digest cycle. Since 1.3, however, it only calls the filter if the value was changed, in our last sample, it would never call the filter again - 'WelcomeTo' would never change.
Luckily the fix is very simple, you'd just need to add to the filter the following:
JSFiddle
myFilter.$stateful = true;
Finally, while dealing with this issue, I had another problem - I needed to use a filter to get async values that could change - Specifically, I needed to fetch translations for a single language, but once the user changed the language, I needed to fetch a new language set. Doing that, proved a bit more tricky, though the concept is the same. This is that code:
JSFiddle
var app = angular.module("app",[]);
debugger;
app.controller("TestCtrl", function($scope, translationService) {
$scope.changeLanguage = function() {
translationService.currentLanguage = "ru";
}
});
app.service("translationService", function($timeout) {
var self = this;
var translations = {"en": {"WelcomeTo": "Welcome!!", "GoodBye": "BYE"},
"ru": {"WelcomeTo": "POZHALUSTA!!", "GoodBye": "DOSVIDANYA"} };
this.currentLanguage = "en";
this.getTranslation = function(placeholder) {
return $timeout(function() {
return translations[self.currentLanguage][placeholder];
}, 2000);
}
})
app.filter("translate", function($timeout, translationService) {
// Sample object: {"en": {"WelcomeTo": {translation: "Welcome!!", processing: false } } }
var translated = {};
var isWaiting = false;
myFilter.$stateful = true;
function myFilter(input) {
if(!translated[translationService.currentLanguage]) {
translated[translationService.currentLanguage] = {}
}
var currentLanguageData = translated[translationService.currentLanguage];
if(!currentLanguageData[input]) {
currentLanguageData[input] = { translation: "", processing: false };
}
var translationData = currentLanguageData[input];
if(!translationData.translation && translationData.processing === false)
{
translationData.processing = true;
translationService.getTranslation(input).then(function(translation) {
console.log("GetTranslation done");
translationData.translation = translation;
translationData.processing = false;
});
}
var translation = translationData.translation;
console.log("Translation for language: '" + translationService.currentLanguage + "'. translation = " + translation);
return translation;
};
return myFilter;
});

Categories