AngularJS: Why won't me $interval stop? - javascript

I've read the docs but it's 3am and I'm at the end of me teather. Here's my controller:
controller('makeDashCtrl', function ($scope, $rootScope, $cookies, $location, $http, $interval) {
var userId = $cookies.get('user_id');
var orgId = $cookies.get('org_id');
$http.get('/api/consolidateProfile/').then(function(data){
console.log(data);
}, function(res){
console.log(res + " - Eh, consolidateProfile probably timed out, no biggie")
})
var testStart = $interval(function(){
$http.get('/api/testProgress/' + userId).then(function(obj){
$scope.message = obj.data.message;
$scope.sub = obj.data.sub;
if(obj.data.nextPhase){
console.log("Should be cancelling...");
nextScrape();
$interval.cancel(testStart); // This one cancels fine
}
}, function(err){
$interval.cancel(testStart);
});
}, 1000);
function nextScrape(){
console.log("In checkScraperadsfsdfads!")
var checkScraper = $interval(function(){
console.log("In checkScraper REAL!")
$http.get('/api/checkScraper/' + userId).then(function(obj){
var msg = JSON.parse(obj.data);
console.log("Got some data!")
console.log(obj);
if(msg.doneFbs){
$scope.fbMsg = "We've finished gathering " + msg.doneFbs + " Facebook accounts";
}
if(msg.doneTwits){
$scope.twitMsg = "We've finished gathering " + msg.doneTwits + " Twitter accounts";
}
$scope.message = msg.message;
$scope.sub = msg.sub;
if(msg.finished){
$interval.cancel(checkScraper); // This does NOT cancel
console.log("Scraping Done!")
$location.path('/dashboard') // This successfully redirects to the next page
}
},function(err){
console.log("There was an error in Checkscraper ")
console.log(err)
$interval.cancel(checkScraper); // This does NOT cancel when there's an error
});
}, 3000)
}
})
See comments in the code above. Perhaps it's an issue within the nextScrape() function, but I cannot get that $interval to cancel. Even when the $location.path() changes to the next page, the interval is still running.
What am I doing wrong here?

You might have multiple ajax requests at a time if your call takes more than a second to get back. Instead of using $interval, why don't you make one call and use $tiemout to schedule a second call if you need to when the first call is done?
function checkProgress() {
$http.get('/api/testProgress/' + userId).then(function(obj){
$scope.message = obj.data.message;
$scope.sub = obj.data.sub;
if(obj.data.nextPhase){
// move on to next phase
nextScrape();
} else {
// not done, schedule another check
$timeout(checkProgress, 1000);
}
}, function(err){
// error, you cancel, but maybe schedule another progress check?
});
}

I suspect the code is making multiple call before receiving response from the Ajax. That means your ajax is taking more than one second to respond. This is happening because you have mentioned very less amount of time to pool the data from server again.
To deactivate the $interval after page redirection you could take a use of $destroy event on scope. Events needs to be clear manually, because they won't get vanished until we detach them. You could stop interval while leaving the page. Destroy the event on scope event
Code
$scope.$on('$destroy', function(){
$interval.cancel(testStart);
})

Related

Bug: angular.js edit form sends two put requests

I can't reproduce this bug or figure out the cause of it: A user edits a form and submits, the changes are saved via PUT request, there is a GET request for data reload, but sometimes there is a second PUT request reverting the changes to the original values.
I've checked the logs and this second PUT request happens within seconds of the first PUT request. I have yet to reproduce this and the user says they did not edit a second time.
In the edit controller:
$scope.save = function() {
$scope.requestPending = true;
APIResources.userResource($route.current.userType,$route.current.params.id).update(makeUpdateObject()).$promise.then(function(data) {
$scope.userRefresh();
$scope.requestPending = false;
$rootScope.$broadcast('app.event.user.updated');
},
function(error){
console.log('Error saving', error);
});
$scope.$dismiss();
};
In the user controller:
$scope.userRefresh = function() {
APIResources.userResource($route.current.userType,$route.current.params.id).get().$promise.then(function(data) {
$scope.user = data;
// Time to reload the data is 2000ms to support 2G (450kbs) or higher
window.setTimeout(function() {
$rootScope.$broadcast('app.user.loaded', data, userType);
}, 2000);
}, function(err) {
console.log('Data failed to load.');
});
};

Show loading overlay until $http get images are loaded

I want to show a loading animation (ideally that shows % of how much is loaded) whilst content loads from my $http get.
I have made an attempt, but it does not seem to hide the content I am trying to hide.
I set a time length- but I do not want it to show the loading overlay for a set time. I want it to show the loading overlay (possibly until a minimum of 3 images are loaded?) until the element is loaded.
Here is my attempt in a plunker:
http://plnkr.co/edit/7ScnGyy2eAmGwcJ7XZ2Z?p=preview
.factory('cardsApi', ['$http', '$ionicLoading', '$timeout', function ($http, $ionicLoading, $timeout) {
var apiUrl = 'http://mypage.com/1/';
$ionicLoading.show({
duration: 3000,
noBackdrop: true,
template: '<p class="item-icon-left">Loading stuff...<ion-spinner icon="lines"/></p>'
});
var getApiData = function () {
return $http.get(apiUrl).then($ionicLoading.hide, $ionicLoading.hide);
};
return {
getApiData: getApiData,
};
}])
.controller('CardsCtrl', ['$scope', 'TDCardDelegate', 'cardsApi', '$http',
function ($scope, TDCardDelegate, cardsApi, $http) {
$scope.cards = [];
cardsApi.getApiData()
.then(function (result) {
console.log(result.data) //Shows log of API incoming
$scope.cards = result.data;
$scope.product_id = result.data.product_id;
})
.catch(function (err) {
//$log.error(err);
})
Remove the duration line from your $ionicLoading.show declaration.
duration: 3000,
So that it looks like:
$ionicLoading.show({
noBackdrop: true,
template: '<p class="item-icon-left">Loading stuff...<ion-spinner icon="lines"/></p>'
});
And that should work (at least it does in the plunker). The duration property specifies when to close the ionicLoading instance and does not wait for ionicLoading.hide().
You want to wait until the image is actually loaded and rendered, but you are hiding the loading messages as soon as the API call returns. From your code it looks as though the API returns the image URL, not the image data itself?
In which case you could do it using the element.onload(), however the problem with this is that it's no longer a generic API which works for loading anything but I'll let you decide whether that's OK for your use case.
var imagesLoaded = 0;
var loadImage = function(result) {
var image = new Image();
image.onload = function () {
imagesLoaded++;
if (imagesLoaded >= 3)
$ionicLoading.hide();
};
// We still want to hide the loading message if the image fails to load - 404, 401 or network timeout etc
image.onerror = function () {
imagesLoaded++;
if (imagesLoaded >= 3)
$ionicLoading.hide();
};
image.src = result.image_url;
};
// We still want to hide the loading message if the API call fails - 404, 401 or network timeout etc
var handleError = function() {
imagesLoaded++;
if (imagesLoaded >= 3)
$ionicLoading.hide();
};
var getApiData = function () {
return $http.get(apiUrl).then(loadImage, handleError);
};

"$apply already in progress" when opening confirm dialog box with Firefox

I am (sometimes) getting a weird $apply already in progress error when opening a confirm dialog box in the following and innocent looking situation :
var mod = angular.module('app', []);
mod.controller('ctrl', function($scope, $interval, $http) {
$scope.started = false;
$scope.counter = 0;
// some business function that is called repeatedly
// (here: a simple counter increase)
$interval(function() {
$scope.counter++;
}, 1000);
// this function starts some service on the backend
$scope.start = function() {
if(confirm('Are you sure ?')) {
return $http.post('start.do').then(function (res) {
$scope.started = true;
return res.data;
});
};
};
// this function stops some service on the backend
$scope.stop = function() {
if(confirm('Are you sure ?')) {
return $http.post('stop.do').then(function (res) {
$scope.started = false;
return res.data;
});
};
};
});
// mock of the $http to cope with snipset sandbox (irrelevant, please ignore)
mod.factory('$http', function ($q) {
return {
post: function() {
return $q.when({data:null});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="ctrl">
<button ng-disabled="started" ng-click="start()">Start</button>
<button ng-disabled="!started" ng-click="stop()">Stop</button>
<br/><br/>seconds elapsed : {{counter}}
</div>
</div>
The error message is :
$rootScope:inprog] $apply already in progress http://errors.angularjs.org/1.2.23/$rootScope/inprog?p0=%24apply
And the callstack is :
minErr/<#angular.js:78:12
beginPhase#angular.js:12981:1
$RootScopeProvider/this.$get</Scope.prototype.$apply#angular.js:12770:11
tick#angular.js:9040:25
$scope.start#controller.js:153:8
Parser.prototype.functionCall/<#angular.js:10836:15
ngEventHandler/</<#angular.js:19094:17
$RootScopeProvider/this.$get</Scope.prototype.$eval#angular.js:12673:16
$RootScopeProvider/this.$get</Scope.prototype.$apply#angular.js:12771:18
ngEventHandler/<#angular.js:19093:15
jQuery.event.dispatch#lib/jquery/jquery-1.11.2.js:4664:15
jQuery.event.add/elemData.handle#lib/jquery/jquery-1.11.2.js:4333:6
To reproduce :
use Firefox (I could not reproduce it with Chrome or IE)
open the javascript console
click alternatively the start and stop buttons (and confirm the dialogs)
try multiple times (10-20x), it does not occur easily
The problem goes away if I remove the confirm dialog box.
I have read AngularJS documentation about this error (as well as other stackoverflow questions), but I do not see how this situation applies as I do not call $apply nor do I interact directly with the DOM.
After some analysis, it seems to be a surprising interaction between the $interval and modal dialog in Firefox.
What is the problem ?
The callstack shows something strange : an AngularJS function tick is called within the controller's start function. How is that possible ?
Well, it seems that Firefox does not suspend the timeout/interval functions when displaying a modal dialog box : this allows configured timeout and intervals callback to be called on the top of the currently executing javascript code.
In the above situation, the start function is called with an $apply sequence (initiated by AngularJS when the button was clicked) and when the $interval callback is executed on the top the start function, a second $apply sequence is initiated (by AngularJS) => boom, an $apply already in progress error is raised.
A possible solution
Define a new confirm service (adapted from this and that blog posts) :
// This factory defines an asynchronous wrapper to the native confirm() method. It returns a
// promise that will be "resolved" if the user agrees to the confirmation; or
// will be "rejected" if the user cancels the confirmation.
mod.factory("confirm", function ($window, $q, $timeout) {
// Define promise-based confirm() method.
function confirm(message) {
var defer = $q.defer();
$timeout(function () {
if ($window.confirm(message)) {
defer.resolve(true);
}
else {
defer.reject(false);
}
}, 0, false);
return defer.promise;
}
return confirm;
});
... and use it the following way :
// this function starts some service on the backend
$scope.start = function() {
confirm('Are you sure ?').then(function () {
$http.post('start.do').then(function (res) {
$scope.started = true;
});
});
};
// this function stops some service on the backend
$scope.stop = function() {
confirm('Are you sure ?').then(function () {
$http.post('stop.do').then(function (res) {
$scope.started = false;
});
});
};
This solution works because the modal dialog box is opened within an interval's callback execution, and (I believe) interval/timeout execution are serialized by the javascript VM.
I was having the same problem in Firefox. Using window.confirm rather than just confirm fixed it for me.

Need to Find the Duration between PageLoad and Click for Next Page in AngularJS

I am new to AngularJS and I want to write a simple function which calculates the number of seconds a user spent on the page from the page loading to them clicking on the 'next' button. So, basically I need to know how much time a user spent on each page. Can anyone help me write this function in AngularJS?
Put this in your app.js file where your app is defined. To do this you need to inject $location and add a listener to '$locationChangeStart'. All that's left is just doing some math and logging. I added the extra code to only log when a page was open for longer than .02. Angular was doing some routing that was getting logging I beleive. So trial and error with the logging.
Doing it with this approach is cleaner in that you don't need to put it in all controllers.
var mainApp = angular.module('mainApp', [.....]);
mainApp.run(function ($rootScope, $location) {
$rootScope.currentPage = "";
$rootScope.pageLoadedTime = "";
$rootScope.$on('$locationChangeStart', function (event) {
if ($rootScope.currentPage != "") {
var seconds = (new Date().getTime() - $rootScope.pageLoadedTime.getTime()) / 1000;
if (seconds > .02)
console.log("Page: ", $rootScope.currentPage, " Seconds spent on page : ", seconds);
}
$rootScope.pageLoadedTime = new Date();
$rootScope.currentPage = $location.url();
});
});
Start by adding event listeners:
document.addEventListener("blur", onBlur, false);
document.addEventListener("focus", onFocus, false);
Then create the onBlur and onFocus functions:
function onFocus() {
$scope.blurTime= Date.now();
log.debug('User time caught at ' + $scope.blurTime);
}
function onBlur() {
var pageTime = Date.now() - $scope.pauseTime,
log.debug('User continue - time is ' + pageTime + 'ms');
}
Do what you need to do within those functions. Warning: I did not test this out, so you'll ned to do some tweeking (check the events).

Angular hide alert message again after x seconds or page change

I'm quite new to angular, now I was able to show an alert message when someone requests a new password, from our app:
Usermodel:
.service('userModel', ['$q', '$http', 'authorizedTracker', function($q, $http, authorizedTracker) {
this.passwordreset = function(token) {
var deferred = $q.defer();
$http({
method: 'GET',
url: '/reset/'+token
})
.then(function(response) {
if (!_(response.data).has('user')) {
deferred.reject('unkown user id');
}
model.resetted = true;
}, function(error) {
deferred.reject(error.data.message);
});
authorizedTracker.addPromise( deferred.promise)
return deferred.promise;
};
So if resetted is true, the message will show up, see below:
html:
<!-- Succes-->
<div class="alert alert-success animated fadeInDown" ng-cloak ng-show="userModel.resetted">
<strong><i class="icon-attention"></i>Success!</strong> New password is sent to your e-mail
</div>
But now I want to hide the alert after x amount of seconds, or if the user clicks to another page. How is that possible? Any solution?
you should use the $timeout service (symilar to the window.setTimeout in native javascript).
In your code you should add the following code
...
model.resetted = true;
$timeout(function() {
model.resetted = false;
}, xxx)//replace xx by the amount of milisecond you want
here is an example, hope it will help you
http://plnkr.co/edit/Qt39x5Xo5JhP61QHYLwO?p=preview
To close after X seconds use the $timeout service.
model.resetted = true;
$timeout(function() {
model.resetted = false;
}, X); // X in milliseconds
For angular2+ users:
You should add timeout to the method witch is showing alert:
setTimeout(() => this.hideAlert(), X);
where hideAlert makes a logic needed to hide an alert and X is number of miliseconds
the idea behind this is to start counting X miliseconds until this.hideAlert is called
arrow function is needed to keep original context (be able to use this as we are used to - pointing to parent class for example)

Categories