Binding function to $interval - angularjs

I have a service that I'm using to manage the state, like the time since a user last did something. In another directive, I use the interval service to check this state (lastActivity), but it's not getting updated:
// myservice
start() {
this.$document.on(this.DOMevents, this.updateActivity);
}
stop() {
this.$document.off(this.DOMevents, this.updateActivity)
}
updateActivity() {
this.lastActivity = new Date();
}
// other directive
$interval(() => {
console.log(myservice.lastActivity.getTime()); // lastActivity is just a javascript Date
}
}, 1000);
I think this is the closure problem in that $interval gets myservice before it runs every 1000 ms so the time is always the same. Is there a way to pass that in to get updated? I see in the docs https://docs.angularjs.org/api/ng/service/$interval
that there is a pass option as the last parameter, but since I'm using TypeScript and an older version of Angular, I won't be able to use this feature. Is there another way to do this?
Maybe this explains my problem clearer:
var now = new Date().getTime();
$interval(() => {
console.log(now);
}, 1000);
I have some service that is keeping track of time amongst other things. In another directive, after a certain amount of time has passed I want to check this value and do some logic. Problem is, now is always the same time. So in myservice, the lastActivity time is not getting read properly in the $interval function.

Related

AngularJS Why is this controller variable not getting updated in the UI?

I'm having a controller using StompJS to subscribe to a url (back-end is Spring Java) that returns an alternating string "list" and "box" every 5 seconds. I want to update my UI element when StompJS receives some data, but I couldn't get the UI element to update. I've test the same logic with a $timeout and the UI is getting updated so it must has something to do with the way callback function works. Can anyone see what is the reason UI is not updating?
I have these simple UI elements:
<input ng-model="ctrl.uniqueId"/>
<input ng-model="test"/>
ctrl.uniqueId is to verify whether the actual controller instance is being updated. For some reason, only 1 controller is making 5 different subscribes every time. If someone can help with that, it'd be great too but I doubt you can get much info unless you see all my codes setup.
Anyway, in my controller (tried self.test and it didn't work so I tried with $scope.test to see if it makes a difference):
self.uniqueId = window.performance.now();
$scope.test = 'list';
// the UI will be updated to dummy after 3 seconds.
$timeout(function() {
$scope.test="dummy";
}, 3000);
// the UI will not update.
var callBackFn = function(progress) {
$scope.test = progress;
console.log(self.uniqueId + ": " + $scope.test);
};
// the server returns alternating data (list and box) every 5 seconds
MyService.subscribeForUpdate('/topic/progressResults', callBackFn);
This is my service's code for StompJS if that matters:
self.subscribeForUpdate = function(channelUrl, callBackFn) {
self.socket.stomp.connect({}, function() {
self.socket.subscription = self.socket.stomp.subscribe(channelUrl,
function (result) {
//return angular.fromJson(result.body);
callBackFn(result.body);
return result.body;
}
);
});
};
This is console.log results:
1831.255000026431: list
1831.255000026431: box
Extra: is it possible to get the return data without callback function similar to Promise?
Be sure to use $apply:
app.service("myService", function($rootScope) {
var self = this;
self.subscribeForUpdate = function(channelUrl, callBackFn) {
self.socket.stomp.connect({}, function() {
self.socket.subscription = self.socket.stomp.subscribe(channelUrl,
function (result) {
//return angular.fromJson(result.body);
$rootScope.$apply(function() {
callBackFn(result.body);
});
return result.body;
}
);
});
};
})
AngularJS modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and AngularJS execution context. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc... You can also use $apply() to enter the AngularJS execution context from JavaScript.
Keep in mind that in most places (controllers, services) $apply has already been called for you by the directive which is handling the event. An explicit call to $apply is needed only when implementing custom event callbacks, or when working with third-party library callbacks.
For more information, see
AngularJS Developer Guide - Integration with the browser event loop
This is a very common issue and happens when a 3rd-party library(out of the angular environment) is used with angularjs. In such cases you need to manually trigger a digest cycle using the:
$scope.$apply()
After that all angular bindings will be updated. Using $timeout (even without timeValue) has the same result as it also triggers $apply()

AngularJS: Nested http call doesn't update the view

In a particular scenario, I need to call the the github api to retrieve a specific user's info. Then issue a second call to retrieve the user's repositories:
search(login: string): void {
this.user = undefined;
// first call
this.githubApi.getUser(login)
.then(user => {
this.user = user;
// second call
this.githubApi.getRepos(user.repos_url)
.then(reposResponse => {
this.repos = reposResponse.repos;
// I don't like to call this.$scope.$apply() !!;
});
});
}
The first call gets executed and the bound elements to this.user gets updated with no problem in the view.
The second call gets executed and the result is returned successfully and this.repos is set correctly. But, the bound elements on the view are not updated.
If I call this.$scope.$apply() in the very last line of the second callback, it makes the view update work but I guess this is not correct approach.
Any solution?
Well, if you are not willing to use $scope.apply();, try updating your getRepos service response code with:
setTimeout(
() => {
this.repos = reposResponse.repos;
}, 0
)
First you need to know , why Angular-Js is not updating the view.
You have used $scope.$apply(), so I'm assuming you already know , how it works and why we use it. Now , to the problem!
Sometimes when you make a callback - nested callback in particular - Angular does not update the view. Sometimes angular thinks that it does not need to update the view because of callbacks. And the watchers do not take action when the value changes of the variable that they are watching.
Then you use $scope.$apply() to run the digest cycle again (assuming you already know the digest cycle if you don't then let me know). And it makes the watchers to update the view.In your case, digest cycle is not running, that is why angular is not updating the view. If your digest cycle was running , angular would have given you error. So, it will tell angular to run digest cycle again because two-way binding is not working properly.
I don't think there is another way. But if there is a way, I would love to know that way. Also its not a bad approach. It was made for these kind of problems.

Autosave form fields for every 15 sec in AgularJS without using $watch

I want to save a form field values every 15 sec with out using $watch and also it should stop executing once it has been moved to a different form. In the form I will be having many fields so I think $ watch will be having performance issue and also am not sure how to call all fields at once for %watch. So I decided to use $interval but I want to stop this execution once I moved to different controller or different form. If user comes back again to this form again this interval function should start automatically. please would you suggest me best way to handle this.
Use $interval like you are planning to do but assign it to a variable
with $scope.on('$destroy', function() { }); callback you can destroy the interval when switching to a different controller.
var intervalPromise = $interval(function() { // your code }, 15000);
$scope.$on('$destroy',function(){
if(intervalPromise)
$interval.cancel(intervalPromise);
});

How to watch for changes in a scope variable manipulated by a service

I need to watch for changes in a countdown variable to run a task when the time is over.
I'm using a service to update the variable, as you can see in this plunker:
http://plnkr.co/edit/NJxqXcD2nhDKq4Q99Bgt
$scope.$watch('time', function (time) {
// This portion of code is reached only twice:
// Once when undefined and after first update.
console.log(time);
});
How can I watch for every change or trigger my task from the service (despite I think this is the wrong choice)?
Watcher fires only twice since reference to time not changed. You have two options: turn on object equality (slow) or specify your own value provider:
$scope.$watch('time', function () {
}, true); // Tells to check for object equality
$scope.$watch(function () {
return ($scope.time || {}).now;
}, function (now) {
// magic
});
But i think you should consider to use $timeout service and promises to run task.

Updating "time ago" values in Angularjs and Momentjs

Original: I have a table generated with ng-repeat with hundreds of entries consisting of several different unix timestamps. I'm using moment.js to make them display like "19 minutes ago" or however long ago it was. How would I have these update every five minutes, for example, without having to refresh the entire table (which takes a few seconds and will interrupt the user's sorting and selections).
I use the following filter. Filter updates value every 60 seconds.
angular
.module('myApp')
.filter('timeAgo', ['$interval', function ($interval){
// trigger digest every 60 seconds
$interval(function (){}, 60000);
function fromNowFilter(time){
return moment(time).fromNow();
}
fromNowFilter.$stateful = true;
return fromNowFilter;
}]);
And in html
<span>{{ myObject.created | timeAgo }}</span>
I believe filters are evaluated every digest cycle, so using a filter to display your "time ago" strings might get CPU-expensive with hundreds of entries.
I decided to go with a pubsub architecture, using essentially eburley's suggested approach (mainly because I don't have to watch for $destroy events and manually unsubscribe), but with a NotificationService rather than a "Channel" function:
.factory('NotificationService', ['$rootScope',
function($rootScope) {
// events:
var TIME_AGO_TICK = "e:timeAgo";
var timeAgoTick = function() {
$rootScope.$broadcast(TIME_AGO_TICK);
}
// every minute, publish/$broadcast a TIME_AGO_TICK event
setInterval(function() {
timeAgoTick();
$rootScope.$apply();
}, 1000 * 60);
return {
// publish
timeAgoTick: timeAgoTick,
// subscribe
onTimeAgo: function($scope, handler) {
$scope.$on(TIME_AGO_TICK, function() {
handler();
});
}
};
}])
A time-ago directive registers/subscribes a timestamp (post.dt in example HTML below) with the NotificationService:
<span time-ago="post.dt" class="time-ago"></span>
.directive('timeAgo', ['NotificationService',
function(NotificationService) {
return {
template: '<span>{{timeAgo}}</span>',
replace: true,
link: function(scope, element, attrs) {
var updateTime = function() {
scope.timeAgo = moment(scope.$eval(attrs.timeAgo)).fromNow();
}
NotificationService.onTimeAgo(scope, updateTime); // subscribe
updateTime();
}
}
}])
A few comments:
I tried to be efficient and not have the directive create a new scope. Although the directive adds a timeAgo property to the scope, in my usage, this is okay, since I only seem to use the time-ago directive inside an ng-repeat, so I'm just adding that property to the child scopes created by ng-repeat.
{{timeAgo}} will be examined every digest cycle, but this should be faster than running a filter
I also like this approach because I don't have a timer running on every timestamp. I have one timer running in the NotificationService.
Function timeAgoTick() could be made private, since likely only the NotificationService will need to publish the time ago event.
Use angular's $timeout service (just a wrapper around setTimeout()) to update your data. Note the third parameter which indicates Angular should run a $digest cycle that will update your data bindings.
Here's an example: http://jsfiddle.net/jandersen/vfpDR/
(This example updates every second so you don't have to wait 5 min to see it ;-)
I ended up having $scope.apply() be called every five minutes by setInterval which reapplies the filters formatting the timestamps into "x minutes ago". Maybe a bit hacky but it works. I'm certain it isn't the optimal solution.
I made a wrapper for momentjs https://github.com/jcamelis/angular-moment
<p moment-interval="5000">{{"2014-05-21T14:25:00Z" | momentFromNow}}</p>
I hope it works for you.

Resources