Implementing a delay ($timeout) to a $http request with AngularJS - javascript

I have a particular function dependant on a (previously executed) service that will trigger multiple queries to a 3rd party API. This API with cancel the requests if it gets too many of them too quick. Is there a way to set a delay to the $http query?
Here is my $http code:
$scope.getImages = function (title, name) {
$http.get('http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=e8aefa857fc74255570c1ee62b01cdba&artist=' + name + '&album='+ title +'&format=json').
success(function(data4) {
$scope.images = data4;
});
}
EDIT Upon request I paste my whole JS (probably I should have done so from the start)
angular.module('myApp', ['ngResource'])
function Ctrl($scope, $http) {
var search = function(name) {
if (name) {
$http.get('http://api.discogs.com/database/search?type=artist&q='+ name +'&page=1&per_page=5').
success(function(data3) {
$scope.clicked = false;
$scope.results = data3.results;
});
}
$scope.reset = function () {
$scope.sliding = false;
$scope.name = undefined;
}
}
$scope.$watch('name', search, true);
$scope.getDetails = function (id) {
$http.get('http://api.discogs.com/artists/' + id).
success(function(data) {
$scope.artist = data;
});
$http.get('http://api.discogs.com/artists/' + id + '/releases?page=1&per_page=10').
success(function(data2) {
$scope.releases = data2.releases;
});
$scope.clicked = true;
$scope.sliding = true;
}
$scope.getImages = function (title, name) {
$http.get('http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=e8aefa857fc74255570c1ee62b01cdba&artist=' + name + '&album='+ title +'&format=json').
success(function(data4) {
$scope.images = data4;
});
}
};

I assume you will want to debounce your calls somehow, something simple like this should work. It will make the call every 300ms unless search(name) is called again before it fires.
var debounce = null;
var search = function(name) {
$timeout.cancel(debounce);
if (name) {
debounce = $timeout(function() {
$http.get('http://api.discogs.com/database/search?type=artist&q='+ name +'&page=1&per_page=5')
.success(function(data3) {
$scope.clicked = false;
$scope.results = data3.results;
});
},300);
}
$scope.reset = function () {
$scope.sliding = false;
$scope.name = undefined;
}
}

Related

setTimeout and clearTimeout interruption

I'm trying to make like a drop alert queue.
So far i've achieved it. The problem comes when I try to implement interruptions.
Let's say that an offline event fires one alert, and before it end's it's timeout, online event fires. I want it to kill offline alert, and show online alert instead.
The thing is that timeout's aren't working as I expect them to, in that case.
Here's the Angular service that handles the alerts:
.factory('AlertDrop', ['$rootScope', '$q', function ($rootScope, $q) {
var timeout = 2000;
$rootScope.DropAlerts = [];
var hideonclick = false;
var listenerOn = false;
var t = this;
this.currentAlert = {};
var getColor = function (clase) {
switch (clase) {
case 'error':
return 'rgba(201,48,44,0.9)';
break;
case 'warning':
return 'rgba(240,173,78,0.9)'
break;
case 'success':
return 'rgba(68,157,68,0.9)'
break;
case 'info':
return 'rgba(49, 176, 213,0.9)'
break;
default:
console.log('Código de color incorrecto. Cargo color por defecto.');
return 'rgba(152,26,77,0.9)';
break;
}
};
this.requestTypes = [
{
type: 'offline',
class: 'error'
},
{
type: 'online',
class: 'success'
}
];
this.bgcolor = 'rgba(201,48,44,0.9)';
this.showAlert = function (params) {
this.currentAlert = params;
delete t.ctout;
t.ctout = {};
var deferred = $q.defer();
if (params.timeout) {
this.timeout = params.timeout
}
if (params.hideonclick) {
hideonclick = params.hideonclick;
}
if (params.class != '')
this.bgcolor = getColor(params.class);
if (params.class != '') {
$('.alertDrop').attr('style', 'background: ' + this.bgcolor + '; top:44px;');
} else {
$('.alertDrop').attr('style', 'top:44px;');
}
if (hideonclick) {
addListener(this.bgcolor);
}
$('.alertDrop').html(params.message);
t.ctout = setTimeout(function () {
timeoutDone();
}, this.timeout);
};
this.hideAlert = function (color) {
$('.alertDrop').css('top', '0');
rmListener();
};
var rmListener = function () {
if (listenerOn) {
$('.alertDrop').unbind('click');
}
};
var addListener = function (color) {
if (!listenerOn) {
// $('.alertDrop').on('click', t.hideAlert(color));
}
};
var timeoutDone = function(){
var deferred = $q.defer();
t.hideAlert(this.bgcolor);
var tt = setTimeout(function(){
t.changeStatus(false);
deferred.resolve(true);
},2000); // 2 segundos, tiempo establecido en style.css
return deferred.promise;
};
var removeOnlines = function(){
$rootScope.DropAlerts = $rootScope.DropAlerts.filter(function(el){
return el.type != 'online';
});
};
var removeOfflines = function(){
$rootScope.DropAlerts = $rootScope.DropAlerts.filter(function(el){
return el.type != 'offline'
});
};
var interrupcion = function(params){
var load = true;
if(params.type == 'offline'){
removeOnlines(params);
load = false;
}else if(params.type == 'online'){
removeOfflines(params);
load = false;
}
if(load){
$rootScope.DropAlerts.push(params);
}else{
clearTimeout(t.ctout);
timeoutDone().then(function(){
t.showAlert(params);
});
}
};
this.checkPush = function(params){
var deferred = $q.defer();
if( $rootScope.AlertDropActive ){
if( params.type == this.currentAlert.type ){
clearTimeout(this.ctout);
setTimeout(function(){
timeoutDone();
},this.timeout)
}else{
interrupcion(params);
}
deferred.resolve(true);
}else{
$rootScope.DropAlerts.push(params);
deferred.resolve(true);
}
return deferred.promise;
};
this.pushAlert = function (params) {
this.checkPush(params);
if (!$rootScope.AlertDropActive) {
this.changeStatus(true);
var alert = $rootScope.DropAlerts.shift();
this.showAlert(alert);
}
};
this.changeStatus = function(v){
$rootScope.AlertDropActive = v;
if (!$rootScope.AlertDropActive && ( $rootScope.DropAlerts.length > 0 )) {
this.changeStatus(true);
var alert = $rootScope.DropAlerts.shift();
this.showAlert(alert);
}
};
return {
showAlert: this.showAlert,
hideAlert: this.hideAlert,
pushAlert: this.pushAlert,
changeStatus: this.changeStatus,
currentAlert: this.currentAlert,
checkPush: this.checkPush
};
}]);
So, when I fire interrupcion function, only in the case that connection is reestablished or lost, I need to stop the current timeout and, once the alert is hidden, show the new event.
I'm displaying this on a div, with a transition of 2 seconds.
Any ideas?
Actually the error wasn't on the code i posted, but when I called pushAlert.
I added a timeout in order to try to simulate fast event changes, and that was messing around with the timeouts.

How can I call a function in an angular controller?

I have a angularJS file like this; I need to call the newGame function in the end of the controller not in html. Can anyone help me with this?
var app = angular.module("myApp", []);
app.controller("myCtrl", function ($scope) {
$scope.newGame = function () {
$scope.random = Math.floor(Math.random() * 100) + 1;
$scope.display = "Start guessing";
};
$scope.giveUp = function () {
$scope.display = $scope.random;
};
$scope.check = function () {
if ($scope.guess > $scope.random) {
$scope.display = "Guess Higher"
} else if ($scope.guess < $scope.random) {
$scope.display = "Guess Lower"
} else {
$scope.display = "You got it!"
}
};
});
It's a function just like any other function. Add $scope.newGame(); at the end of your controller.

Chained promises and prototype `this`

I'm having a hard time to get promises to work with the right this scope inside prototypes.
Here is my code:
'use strict';
angular.module('testApp').factory('UrlSearchApi',
function($resource, URL_SEARCH_API, PAGE_SIZE, $q){
var resource = $resource(URL_SEARCH_API);
resource.Scroll = function () {
return this.reset();
};
resource.Scroll.prototype.reset = function () {
this.visibleItems = [];
this.allItems = [];
this.busy = null;
return this;
};
resource.Scroll.prototype.fetch = function(query){
var params = {};
if(query) { params.q = query; }
return resource.query(params).$promise;
};
resource.Scroll.prototype.loadAllItems = function (results) {
var d = $q.defer();
angular.forEach(results, function (result, i) {
this.allItems.push(result);
if(i === results.length - 1 ) { d.resolve(); }
}, this);
return d.promise;
};
resource.Scroll.prototype.loadVisibleItems = function () {
var length = this.visibleItems.length,
offset = parseInt(length / PAGE_SIZE),
start = PAGE_SIZE * offset,
end = start + PAGE_SIZE,
subset = this.allItems.slice(start, end),
d = $q.defer();
angular.forEach(subset, function (item, i) {
this.visibleItems.push(item);
if(i === subset.length - 1 ) { d.resolve(); }
}, this);
return d.promise;
};
resource.Scroll.prototype.nextPage = function (query) {
if(this.busy) { return; }
console.log('nextPage ', query);
var tasks = [],
that = this;
if(!this.allItems.length) {
this.reset();
this.busy = true;
return this.fetch(query)
.then(this.loadAllItems)
.then(this.loadVisibleItems)
.finally(function () {
this.busy = false;
});
} else {
this.busy = true;
return this.loadVisibleItems().finally(function () {
this.busy = false;
});
}
};
return resource;
});
Whenever I run the tests I get
describe('#nextPage', function () {
var scroll;
describe('when there is NO search term (show all)', function () {
beforeEach(function (done) {
scroll = new UrlSearchApi.Scroll();
$httpBackend.expectGET('/policy/search')
.respond(200, arrayGenerator(123));
scroll.nextPage().then(done);
$httpBackend.flush();
$rootScope.$apply();
});
it('should load all the items in all items variable', function () {
expect(scroll.allItems.length).toBe(123);
});
});
});
I get the following error:
TypeError: 'undefined' is not an object (evaluating 'this.allItems')
I now that $q in strict mode sets the this inside then to undefined. I tried using bind(this) in multiple places but not luck... Any ideas?
I've already answered a question like this here.
Just let me know in comments if you still have questions.
Upd. Try to update your resource.Scroll.prototype.nextPage method like this:
if(!this.allItems.length) {
this.reset();
this.busy = true;
return this.fetch(query)
.then(this.loadAllItems.bind(this)) //bind here
.then(this.loadVisibleItems.bind(this)) // here
.finally(function () {
this.busy = false;
}.bind(this)); //and here
But keep in mind - when you pass a function as a callback to a then or to forEach e.t.c it'll lose this context. So, use bind exactly when you pass the function which uses this syntax as a callback.

Ionic infinite scroll

I am using wordpress as a backend for an app and I want to use infinite scroll but I am having trouble concatenating articles.
I am calling the service using a factory:
.factory('Worlds', function ($http) {
var worlds = [];
storageKey = "worlds";
function _getCache() {
var cache = localStorage.getItem(storageKey );
if (cache)
worlds = angular.fromJson(cache);
}
return {
all: function () {
return $http.get("http://www.examplesite.com/tna_wp/wp-json/posts?filter[category_name]=international&filter[posts_per_page]=10").then(function (response) {
worlds = response.data;
console.log(response.data);
return worlds;
});
},
GetNewPosts: function () {
return $http.get("http://www.examplesite.com/tna_wp/wp-json/posts?filter[category_name]=international&filter[posts_per_page]=2").then(function (response) {
worlds = response.data;
console.log(response.data);
return worlds;
});
},
get: function (worldId) {
if (!worlds.length)
_getCache();
for (var i = 0; i < worlds.length; i++) {
if (parseInt(worlds[i].ID) === parseInt(worldId)) {
return worlds[i];
}
}
return null;
}
}
})
and my controller looks like this:
.controller('WorldCtrl', function ($scope, $stateParams, $timeout, _, Worlds) {
$scope.worlds = [];
Worlds.all().then(function (data){
$scope.worlds = data;
window.localStorage.setItem("worlds", JSON.stringify(data));
},
function (err) {
if(window.localStorage.getItem("worlds") !== undefined) {
$scope.worlds = JSON.parse(window.localStorage.getItem("worlds"));
}
}
);
$scope.loadMore = function() {
Worlds.GetNewPosts().then(function (worlds){
var loadedIdss = _.pluck($scope.worlds, 'id');
var newItemss = _.reject(worlds, function (item){
return _.contains(loadedIdss, item.id);
});
$scope.worlds = newItemss.concat($scope.worlds);
$scope.$broadcast('scroll.infiniteScrollComplete');
});
};
})
I am trying to use underscore to ignore the posts that are already loaded, however when i try the infinite scroll it just goes into a loop calling more posts but doesnt add them to my ng-repeat and ionicLoading renders the app useless.
ion-infinite-scroll works with some sort of paginated result and you seem to feed your list with all the results.
Your API should look something like this:
http://www.examplesite.com/tna_wp/wp-json/posts?filter[category_name]=international&filter[posts_per_page]=2&filter[page]=1
notice I've added a page filter.
and your service responsible to fetch the data should look something like this:
.factory('dataService', function($http) {
return {
GetPosts: function(page, pageSize) {
return $http.get("http://mywebservice/api/test/posts/" + page + "/" + pageSize);
}
};
});
Your controller
.controller('mainController', function($scope, dataService) {
$scope.posts = [];
$scope.theEnd = false;
var page = 0;
var pageSize = 10;
$scope.$on('$stateChangeSuccess', function() {
$scope.loadMore();
});
$scope.loadMore = function(argument) {
page++;
dataService.GetPosts(page, pageSize)
.then(function(result) {
console.log('items fetched: ' + result.data.length);
if (result.data.length > 0) {
angular.forEach(result.data, function(value, key) {
$scope.posts.push(value);
});
}
else {
$scope.theEnd = true;
}
})
.finally(function() {
$scope.$broadcast("scroll.infiniteScrollComplete");
});
};
})
would initialize an array of objetct - as you're doing - and a boolean which tells the directive ion-infinite-scroll when you've reached the end:
$scope.posts = [];
$scope.theEnd = false;
Then you can have some variables to control the pagination:
var page = 0;
var pageSize = 10;
I start loading when the view is loaded:
$scope.$on('$stateChangeSuccess', function() {
$scope.loadMore();
});
$scope.loadMore then would increment the page number:
page++;
and call the service layer:
dataService.GetPosts(page, pageSize)
when I've reached the end of the stream I would set the variable:
$scope.theEnd = true;
to let the directive know we don't have other items to append.
.finally(function() {
$scope.$broadcast("scroll.infiniteScrollComplete");
});
finally is always called when the promise is resolved.
Instead of ng-repeat you can use collection-repeat which should be much faster.
You can play with it here.
Try this create a function infiniteScrollCompleteCancelLoadMore and call it when you want to complete the scroll and you have reached the end of your list.
function infiniteScrollCompleteCancelLoadMore() {
$timeout(function () {
$timeout(function () {
$scope.$broadcast('scroll.infiniteScrollComplete');
$rootScope.canLoad = false;
});
});
}
$scope.loadMore = function() {
Worlds.GetNewPosts().then(function (worlds){
var loadedIdss = _.pluck($scope.worlds, 'id');
var newItemss = _.reject(worlds, function (item){
return _.contains(loadedIdss, item.id);
});
$scope.worlds = newItemss.concat($scope.worlds);
infiniteScrollCompleteCancelLoadMore() // CHANGE HERE
});
};
and your HTML
<ion-infinite-scroll on-infinite="loadMore()" ng-if="canLoad" distance="1%"
immediate-check="false"></ion-infinite-scroll>
OR call This is you just want to cancel loadMore loop.
function infiniteScrollComplete() {
$timeout(function () {
$timeout(function () {
$scope.$broadcast('scroll.infiniteScrollComplete');
});
});
}

Auto refresh in AngularJS

In the following code, on autorefresh the timer starts when all the queries are sent, but I want the timer to start when all the ajax queries are completed.
How can I go about it?
$scope.autoRefresh = function(){
$scope.running = !$scope.running;
$scope.timeoutsec = 10;
if($scope.running)
{
$scope.timer = setInterval($scope.refresh, 10000);
$scope.count = $timeout($scope.countdown,1000);
}
else
{
clearInterval($scope.timer);
$timeout.cancel($scope.count);
}
}
countdown function --
$scope.countdown = function(){
$scope.timeoutsec = $scope.timeoutsec - 1;
$scope.count = $timeout($scope.countdown, 1000);
}
refresh function --
$scope.refresh = function(){
// update function
$scope.timeoutsec = 11;
}
Edit:
update function --
$scope.update() =function(){
for(var i=0;i<cluster.length;i++)
{
var request = $.ajax(cluster[i]).success(function(data, status, headers, config)
{
// on success
});
}
}
If you are using xmlhttp request, you have to set third parameter in xhr.open() function as true. This will make the request synchronous, shown below :
xhr.open(method, url, true);
Here is some boilerplate code for you to see how it could be done.
(function() {
'use strict';
angular.module('app')
.controller('controller', function($scope, $http, $timeout, $q) {
$scope.update = function () {
// single request
$http.get('url')
.then(function (data) {
$timeout($scope.update, 10000);
});
// or with multiple requests
var request1 = $http.get(/****/);
var request2 = $http.get(/****/);
var request3 = $http.get(/****/);
$q.all([request1, request2, request3])
.then(function() {
$timeout($scope.update, 10000);
});
};
});
})();
Initialise $scope.running to be false, only on the press of auto refresh button running should be set to true.
$scope.running = false;
autorefresh function -
$scope.autoRefresh = function(){
$scope.running = !$scope.running;
$scope.timeoutsec = 10;
if($scope.running)
{
$scope.count = $timeout($scope.countdown,1000);
}
else
{
$timeout.cancel($scope.count);
}
}
countdown function --
$scope.countdown = function(){
$scope.timeoutsec = $scope.timeoutsec - 1;
if($scope.timeoutsec == 0)
{
$scope.refresh();
}
whenAll($scope.queries).done(function(){
$scope.count = $timeout($scope.countdown, 1000);
});
refresh function ---
$scope.refresh = function(){
div.style.visibility = "visible";
$scope.update();
$scope.timeoutsec = 11;
}
whenall function --
function whenAll(promises) {
var i, data = [],
dfd = $.Deferred();
for (i = 0; i < promises.length; i++) {
promises[i].done(function(newData) {
data.push(newData);
if (data.length == promises.length) {
dfd.resolve(data);
}
}).fail(function() {
data.push("NO DATA");
if (data.length == promises.length) {
dfd.resolve(data);
}
});
}
return dfd.promise();
}
Push all your ajax queries to $scope.queries.
Push all your $http ajax into an array, like so, since $http return promises, youcan use the $q API to handle when all requests are finished:
var httpPromises = [
$http({method:'GET', url: '/myjson0.json'}),
$http({method:'GET', url: '/myjson1.json'}),
$http({method:'GET', url: '/myjson2.json'}),
$http({method:'GET', url: '/myjson3.json'})
];
//handle your requests like you would normally do
httpPromises[0].success(...).fail(...);
httpPromises[1].success(...).fail(...);
httpPromises[2].success(...).fail(...);
httpPromises[3].success(...).fail(...);
// remember to inject $q in your controller/factory
$q.all(httpPromises,function(){
//start your timer here
});
$q documentation

Categories

Resources