I have to AngularJS services to load data to my app which are set up in essentially the same way, although one works and one does not.
angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives'])
.service('uniqueLists', function ($http) {
console.log("Getting Unique Lists")
var itemsToList = [
'designer',
'store',
'category'
]
var uniqueLists = {};
var promise = (function(){
console.log("Inside Function")
for (var i = 0; i<itemsToList.length; i++){
var item = itemsToList[i];
uniqueLists[item] = [];
$http.get('/api/uniques/' + item, { cache: true}).
success(function (data){
uniqueLists[item] = data.query;
console.log(i + " out of " + itemsToList.length);
if (i == itemsToList.length -1){
return uniqueLists;
}
});
};
})();
return promise;
})
I think my main issue is that the $http call is non-blocking. For instance, when I try to console.log(i + " out of " + itemsToList.length) I see:
3 out of 3
3 out of 3
3 out of 3
In turn, in my app when I try to use uniqueLists like so:
function homeCtrl($scope, $location, uniqueLists, userInfo){
uniqueLists.then(function(obj){
// Do stuff
});
}
I get the error TypeError: Object [object Object] has no method 'then'
Any ideas on how to fix this? The $http.get api call works just fine so I think it's just a blocking/async problem.
Try this untested code
angular.module('myApp', [])
.service('uniqueLists', function ($http, $q) {
console.log("Getting Unique Lists")
var itemsToList = [
'designer',
'store',
'category']
var uniqueLists = {};
var promises = []
console.log("Inside Function")
for (var i = 0; i < itemsToList.length; i++) {
var item = itemsToList[i];
promises.push(http.get('/api/uniques/' + item, {
cache: true
}));
};
var defered = $q.defer();
$q.all(promises).then(function (data) {
for (var i = 0; i < data.length; i++) {
var item = itemsToList[i];
uniqueLists[item] = data[i].query;
}
defered.resolve(uniqueLists);
}, function (error) {
defered.reject(error)
});
return defered.promise;
})
Related
I'm taking the data from different sharepoint pages lists. I'm calling these lists with a Factory.
My code is counting how many items with a "Completed" status each list has.
I need to take the values of each one into an array, but the result is always null.
Here's my example:
<script>
var myApp = angular.module("myApp", []);
myApp.factory("myFactory", ["$http", function($http) {
return {
siteOne: function() {
return $http({
method: "GET",
url: "siteURL/_api/web/lists/getByTitle('List 1')/items",
headers: {"Accept": "application/json; odata=verbose"}
});
},
siteTwo: function() {
return $http({
method: "GET",
url: "siteURL/_api/web/lists/getByTitle('List 2')/items",
headers: {"Accept": "application/json; odata=verbose"}
});
}
}
}]);
myApp.controller("myController", function($scope, $http, myFactory) {
myFactory.siteOne().success(function(data, status, headers, config) {
$scope.projects = data.d.results;
var items = $scope.projects,
totalItems = 0;
for (var i=0;i<items.length;i++) {
var currentItem = items[i];
if(currentItem.Status!="Completed") {
totalItems++;
}
};
$scope.oneItems = totalItems;
});
myFactory.siteTwo().success(function(data, status, headers, config) {
$scope.projects = data.d.results;
var items = $scope.projects,
totalItems = 0;
for (var i=0;i<items.length;i++) {
var currentItem = items[i];
if(currentItem.Status!="Completed") {
totalItems++;
}
};
$scope.twoItems = totalItems;
});
$scope.data = [
$scope.oneItems, $scope.twoItems
];
console.log(JSON.stringify($scope.oneItems));
console.log(JSON.stringify($scope.twoItems));
console.log(JSON.stringify($scope.data));
});
</script>
If I want to print each value separately, it shows the values! But if I try to put them inside the array, it shows the values as "null":
3
5
[null, null]
Why is this happening and how can I fix this? ..am I doing something wrong?
CODE UPDATE
Here is my code already working for those who'd like to see it. I changed the controller as Sergey Mell sugested, using $q, also I'm using AngularJS v1.7.5 (as georgeawg sugested):
myApp.controller("myController", function($scope, $http, myFactory, $q) {
$q.all([
myFactory.siteOne().then(response => {
var items = response.data.d.results,
totalItems = 0;
for (var i=0;i<items.length;i++) {
var currentItem = items[i];
if(currentItem.Status!="Completed") {
totalItems++;
}
};
$scope.oneItems = totalItems;
}),
myFactory.siteTwo().then(response => {
var items = response.data.d.results,
totalItems = 0;
for (var i=0;i<items.length;i++) {
var currentItem = items[i];
if(currentItem.Status!="Completed") {
totalItems++;
}
};
$scope.twoItems = totalItems;
})
]).then(function() {
$scope.data = [
$scope.oneItems, $scope.twoItems
];
console.log(JSON.stringify($scope.data));
});
});
You're putting your data before the requests have been finished. You have to wait till the end of both requests. I'd propose to use Promise.all or $q.all, which is more natural for AngularJS i.e.:
myApp.controller("myController", function($scope, $http, myFactory, $q) {
$q.all([
myFactory.siteOne().then(/* Your stuff here */),
myFactory.siteTwo().then(/* Your stuff here */),
]).then(function() {
$scope.data = [
$scope.oneItems, $scope.twoItems
];
})
Also assign values to key like:
$scope.data = [
dataItemOne: $scope.oneItems,
dataItemTwo: $scope.twoItems ];
You have best option as #Sergey Mell given below use $q service.
If you don't want to use $q.
then
Declare $scope.data = []; at top and use
$scope.data.push($scope.oneItems)
But make sure you have declared $scope.data to [] at the top otherwise it will throw error.
your controller code will be like
myApp.controller("myController", function($scope, $http, myFactory) {
$scope.data = [];
myFactory.siteOne().success(function(data, status, headers, config) {
$scope.projects = data.d.results;
var items = $scope.projects,
totalItems = 0;
for (var i=0;i<items.length;i++) {
var currentItem = items[i];
if(currentItem.Status!="Completed") {
totalItems++;
}
};
$scope.oneItems = totalItems;
$scope.data.push($scope.oneItems);
});
myFactory.siteTwo().success(function(data, status, headers, config) {
$scope.projects = data.d.results;
var items = $scope.projects,
totalItems = 0;
for (var i=0;i<items.length;i++) {
var currentItem = items[i];
if(currentItem.Status!="Completed") {
totalItems++;
}
};
$scope.twoItems = totalItems;
$scope.data.push($scope.twoItems );
});
/* $scope.data = [
$scope.oneItems, $scope.twoItems
];*/
console.log(JSON.stringify($scope.oneItems));
console.log(JSON.stringify($scope.twoItems));
console.log(JSON.stringify($scope.data));
});
I am trying to work with an object array which I am sharing among two controllers one of which is dealing with modal window.
Here is the js code.
angular.module('MyApp', ['ngMaterial', 'ngMessages', 'material.svgAssetsCache', 'ui.bootstrap'])
.service('Faq', function ($http) {
this.faqList = [];
this.faqList = $http.get('/Json/faq.json');
this.getFaqs = function ()
{
return this.faqList;
}
this.addfaq = function (obj) {
this.faqList.push(obj);
};
})
.controller('AppCtrl', function ($scope,$modal,Faq) {
$scope.faqData = [];
Faq.getFaqs().then(function (msg) {
$scope.faqData = msg.data;
});
}
$scope.show = function () {
$modal.open({
templateUrl: "faqAddUpdate.html",
controller: "faqctrl"
});
};
})
.controller('faqctrl', function ($scope, $modalInstance, Faq) {
$scope.question = '';
$scope.id = '';
$scope.answer = '';
$scope.editFaq = function (id) {
$scope.divFaq = true;
$scope.faqs = [];
Faq.getData().then(function (msg) {
$scope.faqs = msg.data;
var l = $scope.faqs.length;
for (var i = 0; i < l; i++) {
if ($scope.faqs[i].id == id) {
$scope.question = $scope.faqs[i].question;
$scope.id = $scope.faqs[i].id;
$scope.answer = $scope.faqs[i].answer;
}
}
});
};
$scope.AddUpdateFAQ = function () {
var faq = {
id: $scope.id,
question: $scope.question,
answer: $scope.answer
};
Faq.addfaq(faq);
console.log(faq);
$modalInstance.close();
};
$scope.Cancel = function () {
$modalInstance.dismiss();
};
});
but when I am submitting the data through the modal it says this.faqList.push is not a function.
It is because your faqList variable is not an array.
You overide the first definition:
this.faqList = [];
With this:
this.faqList = $http.get('/Json/faq.json');
But $http.get returns a promise (see doc), not an array.
You should do something like this:
this.faqList = [];
$http.get('/Json/faq.json').then(function(result) {
// process your results here
this.faqList = result.data;
});
Not tried, but this is within the function scope, so create a _this var first might help:
this.faqList = [];
this.faqList = $http.get('/Json/faq.json');
var _this = this;
this.getFaqs = function ()
{
return _this.faqList;
}
this.addfaq = function (obj) {
_this.faqList.push(obj);
};
So I have a longrunning query status page for my company, and it shows bars for the different database instances that change color based on number of longrunners + other criteria.
The issue is, every time the call is made to update the info, the colors all go back to default, and build from the ground up. This is because I'm using a $scope.variable object to hold the color information as the longrunner data is retrieved.
I want to switch this to using a local standard variable within the function, and only after all data has been retrieved, assign this variable to the $scope.variable.
Context - our instances are organized into swimlanes, so I create an object for the swimlane color and for the instance color. When all are collapsed, you only see the swimlane, so I needed a way for the instance color to bubble up to the swimlane.
So it amounts to something like this:
var getLongrunners = function(){
$scope.longrunnersByInstance = {};
for (var l = 0; l < $scope.swimlanes.length; l++){
$scope.slColor[$scope.swimlanes[l].swimlane] = 0;
}
for (var j = 0; j < instances.length; j++){
$scope.longrunnersByInstance[instances[j].instance] = [];
$scope.instanceColor[instances[j].instance] = 0;
}
for (var i = 0; i < instances.length; i++){
(function(e){
$http
.get('/getLongrunners',{params: {envFlag: '',instance: instances[e].instance}})
.then(function(response){
var longrunners = response.data;
for(var k = 0; k < longrunners.length; k++){
$scope.longrunnersByInstance[instances[e].instance].push(longrunners[k]);
}
if(longrunners.length > $scope.dangerThresh){
$scope.instanceColor[instances[e].instance] = 2;
}else if(longrunners.length >= $scope.warningThresh){
$scope.instanceColor[instances[e].instance] = 1;
}
if($scope.slColor[instances[e].swimlane] < $scope.instanceColor[instances[e].instance]) {
$scope.slColor[instances[e].swimlane] = $scope.instanceColor[instances[e].instance]
}
},getLongrunnersFail);
}(i));
So I want the $scope.slColor and $scope.instanceColor to be regular local variables until this loop finishes.
I look into promises, but that seemed to only be useful with $http before .then() is called on it.
Is there a way to make a custom promise type architecture and contain more than one function, and only return the promise when everything has been completed?
Thanks!
EDIT:
Most recent try:
var promises = [];
var longrunnersByInstance = {};
var instancesPerf = {};
var slColor = {};
var instanceColor = {};
var promiseTest = function() {
$scope.longrunnersByInstance = {};
for (var l = 0; l < $scope.swimlanes.length; l++){
slColor[$scope.swimlanes[l].swimlane] = 0;
}
for (var j = 0; j < instances.length; j++){
instanceColor[instances[j].instance] = 0;
}
instances.forEach(function (instance) {
promises.push($http
.get('/getLongrunners', {
params: {envFlag: 'bh', instance: instance.instance}
})
.then(function (response) {
var longrunners = response.data;
longrunnersByInstance[instance.instance] = [];
for (var k = 0; k < longrunners.length; k++) {
longrunnersByInstance[instance.instance].push(longrunners[k]);
}
if (longrunners.length > $scope.dangerThresh) {
instanceColor[instance.instance] = 2;
} else if (longrunners.length >= $scope.warningThresh) {
instanceColor[instance.instance] = 1;
}
console.log(instance.instance);
if (slColor[instance.swimlane] < instanceColor[instance.instance]) {
slColor[instance.swimlane] = instanceColor[instance.instance]
}
return true;
}, getLongrunnersFail)
);
function getLongrunnersFail(response){
console.log("getting longrunners failed" + response.status);
}
$q.all(promises).then(function () {
// longrunnersByInstance to $scope
console.log('calling all promises callback!');
instances.forEach(function (instance) {
$scope.longrunnersByInstance[instance.instance] = longrunnersByInstance[instance.instance];
});
// instancesPerf to $scope
instances.forEach(function (instance) {
$scope.instancesPerf[instance.instance] = instancesPerf[instance.instance];
});
// slColor to $scope
instances.forEach(function (instance) {
$scope.slColor[instance.instance] = slColor[instance.instance];
});
// instanceColor to $scope
instances.forEach(function (instance) {
$scope.instanceColor[instance.instance] = instanceColor[instance.instance];
});
}, allPromisesFail);
function allPromisesFail(){
console.log("all promises failed")
}
});
};
Angular uses the $q service for dealing with promises.
It has a function called all to deal with exactly the type of problem you encountered.
here is a simple fiddle to demonstrate it: http://jsfiddle.net/ThomasBurleson/QqKuk/
var myApp = angular.module('myApp', []);
function MyCtrl($scope, $q, $timeout) {
var thenFn = function(value){
console.log('resolved ', value);
return value;
},
q1 = $scope.q1 = $q.defer(),
q2 = $scope.q2 = $q.defer(),
p1 = $scope.q1.promise,
p2 = $scope.q2.promise;
$scope.fromThen = $q.all([
p1.then(thenFn),
p2.then(thenFn)
])
.then(function(values) {
console.log(values);
return values;
});
// Must start the AngularJS digest process
// to allow $q.resolve() to work properly
// So use $timeOut() or $apply()
setTimeout(function () {
$scope.$apply( function() {
console.log('resolving delayed promises');
q1.resolve({value : 1});
q2.resolve({value : 2});
});
}, 100, this);
/*
* Alternative approach
*
$timeout( function() {
console.log('resolving delayed promises');
q1.resolve({value : 1});
q2.resolve({value : 2});
});
*/
}
Here is how you would apply this to your code (haven't tested it, so it's just a direction, but it should get you going):
var promises = [];
for (var i = 0; i < instances.length; i++){
//$http return a promise, so you can just push it
promises.push( $http
.get('/getLongrunners',{params: {envFlag: '',instance: instances[e].instance}}));
}
$q.all(promises).then(function(values){
//values should contain an array with all the results you got from all the requests, so you can run through it and aggregate the results
});
Promises are chainable: when you return something inside the success callback of a promise you get a new promise that resolves with the returned value.
Example from angular documentation ("Chaining Promises" part):
promiseB = promiseA.then(function(result) {
return result + 1;
});
// promiseB will be resolved immediately after promiseA is resolved and its value
// will be the result of promiseA incremented by 1
So, in your /getLongRunners callbacks you can return a value that immediately resolves (like true) so that you get a promise that resolves as soon as the callback is done. If you collect all these "child" promises in an array you can than pass that array to $.all, and it will resolves when all the promises resolve, i.e. as soon as all the callbacks are done.
Here I replace the for loop and embedded immediately-executed function with the forEach method: it's clearer and avoids the closure problem you encountered
var promises = [];
instances.forEach(function(instance, i) {
promises.push($http
.get('/getLongrunners', {
params: {envFlag: '', instance: instances[e].instance}
})
.then(function(response) {
var longrunners = response.data;
// whatever you have to do
return true;
}, getLongrunnersFail);
});
$q.all(promises).then(function() {
// When you are here, all your callbacks will have been executed
});
I am facing trouble with my angularjs script.
Background: I am trying to determine the location of the user, I have written my business logic in my service from where I am returning the location of the user.
Problem : The result I am getting in my console is as below :
[undefined] landingPage.js:11 undefined landingPage.js:11 undefined
var app = angular.module("PublicEvents", ["geolocation"]);
app.controller("iterator", ["$scope", "$http", "locationService1", function($scope, $http, locationService1){
$scope.targetCity = [];
$scope.targetCity.push(locationService1.location());
console.log($scope.targetCity);
$scope.$watch(function () { return locationService1.cityNameArray; },
function (value) {
$scope.targetCity = value;
console.log($scope.targerCity);
}
);
}]);
app.service("locationService1",['$http','$window', function( $http, $window){
var access = this;
this.location = function(){
$window.navigator.geolocation.getCurrentPosition(function(position) {
access.lat = position.coords.latitude;
access.long = position.coords.longitude;
access.locationData = [];
access.cityNameArray = [];
/*var url = "http://maps.googleapis.com/maps/api/geocode/json?latlng=18.9750,72.8258&sensor=true";*/
var url = "http://maps.googleapis.com/maps/api/geocode/json?latlng="+access.lat+","+access.long+"&sensor=true";
//AJAX CALL TO GET THE LOCATION
$http.get(url).then(function(response) {
access.locationData = response.data;
if(access.locationData.status == "OK" || access.locationData.status==200 ) {
angular.forEach(access.locationData.results, function(value, key){
var len = value.address_components.length;
for(var i = 0; i< len; i++){
if(value.address_components[i].types[0] =="locality" || value.address_components[i].types[0] =="sublocality_level_1"){
access.cityNameArray.push(value.address_components[i].long_name);
}
}
});
};
});
return access.cityNameArray;
});
};
}]);
Seems like you need to return data from an async call and you are returning value from outside the function. I'd suggest you to use promise pattern in such situation.
this.location = function(){
$window.navigator.geolocation.getCurrentPosition(function(position) {
access.lat = position.coords.latitude;
access.long = position.coords.longitude;
access.locationData = [];
access.cityNameArray = [];
/*var url = "http://maps.googleapis.com/maps/api/geocode/json?latlng=18.9750,72.8258&sensor=true";*/
var url = "http://maps.googleapis.com/maps/api/geocode/json?latlng="+access.lat+","+access.long+"&sensor=true";
//return promise from here..
return $http.get(url).then(function(response) {
access.locationData = response.data;
if(access.locationData.status == "OK" || access.locationData.status==200 ) {
angular.forEach(access.locationData.results, function(value, key){
var len = value.address_components.length;
for(var i = 0; i< len; i++){
if(value.address_components[i].types[0] =="locality" || value.address_components[i].types[0] =="sublocality_level_1"){
access.cityNameArray.push(value.address_components[i].long_name);
}
}
});
};
return access.cityNameArray; //returned from success callback
});
});
};
Inside controller you need to use .then function to get data from the service loacation function. You were doing console.log when you are doing async call which doesn't return anything.
locationService1.location().then(function(data){ //success callback.
$scope.targetCity.push(data)
},function(error){ //error callback.
console.log(error)
});
Right now I have this JS bin: http://jsbin.com/uhabed/64/ in which you can hopefully see the infite scroll loading of more images. These images are added when the page bottom is reached by the scroll bar on line 13 of the javascript:
angular.module('infinitescroll', []).directive('onScrolled', function () {
return function (scope, elm, attr) {
var el = elm[0];
elm.bind('scroll', function () {
if (el.scrollTop + el.offsetHeight >= el.scrollHeight) {
scope.$apply(attr.onScrolled);
}
});
};
}).controller("scrollCtrl", function($scope, getStuff){
$scope.data = getStuff;
$scope.loaddata = function(){
var length = $scope.data.length;
for(var i = length; i < length + 10; i ++){
$scope.data.push(i);
}
};
$scope.loaddata();
}).factory('getStuff',function($http) {
var images_list = ["www.google.com","www.facebook.com","www.supercuber.com","www.snappiesticker.com"];
images_list.addStuff = function(){ $http.get("http://jsbin.com/yehag/2").success(function(data){
var returned_list = JSON.parse(data.javascript);
console.log(returned_list);
for (var i=0;i<8;i++){
images_list.push(returned_list[i].name);
}
});
};
console.log(images_list);
return images_list;
});
I want to replace line 13 with $scope.loaddata = images_list.addStuff(); from the factory below. basically to use the $http function and add the data from that instead. images_list is already being returned properly seeing as the first 4 items in the output are the ones defined in the factory on line 21. However, the optomistic $scope.loaddata = images_list.addStuff(); doesn't seem to be working.
How can I pass this function up into the $scope.loaddata?
images_list is an array. You can't arbitrarily assign a property to it like images_list.addStuff
Create an object and return that object from the factory
factory('getStuff',function($http) {
var images_list = ["www.google.com","www.facebook.com","www.supercuber.com","www.snappiesticker.com"];
var addStuff = function(){....};
return{
images_list: images_list,
addStuff: addStuff
}
});
Then in controller:
$scope.data = getStuff.images_list;
$scope.loaddata = getStuff.addStuff
This is not a clean way of having an array-like object that has additional properties on it, however the above answer that you 'cannot add functions onto an array' is incorrect. While creating array-like objects is kind of messy and should be avoided where possible. If you feel it is absolutely necessary, I would handle it like this (this is similar to how jQuery does it.
function ImagesList($http) {
this.push.apply(this, [
"www.google.com",
"www.facebook.com",
"www.supercuber.com",
"www.snappiesticker.com"
]);
this._$http = $http;
}
ImagesList.prototype = {
push: [].push,
splice: [].splice,
pop: [].pop,
indexOf: [].indexOf,
addStuff: function () {
this._$http.get("http://jsbin.com/yehag/2").then(function(data){
var returnedList = angular.toJson(data.javascript);
for (var i=0; i<8; i++) {
this.push(returnedList[i].name);
}
}.bind(this));
}
};
angular
.module('infinitescroll', [])
.service('imageList', ImagesList);
.directive('onScrolled', function () {
return {
scope: {
onScrolled: '&'
},
link: function (scope, elm, attr) {
var el = elm[0];
// Original implementation will end up causing memory leak because
// the handler is never destroyed. Use as follows
elm.on('scroll.iScroll', function () {
if (el.scrollTop + el.offsetHeight >= el.scrollHeight) {
scope.$apply(attr.onScrolled);
}
}).on('$destroy', function () {
elm.off('.iScroll');
});
}
};
}).controller("scrollCtrl", function($scope, imageList){
$scope.data = imageList;
$scope.loaddata = function(){
var length = $scope.data.length;
for(var i = length; i < length + 10; i++){
$scope.data.push(i);
}
};
$scope.loaddata();
})