I'm new to AngularJS, coming from a jQuery background. At the moment I take my first steps and create some sample pages with Angular.
Now I wanted to achieve some thing like this: Think of an app with two "pages". First is the main page with some text etc and on the other theres running a timer. The timer should start from 0 and count up to infinite by every second, right when you started the app. This is the first thing I cannot achieve. My timer only starts when I navigate to the second page.
The second problem: When the timer is running, I want to navigate through all the pages in the app and the timer should still count in the background. Well, at the moment it does run in the background, but every time when I navigate to the page with the timer, it seems like there gets opened another task that count the timer up, resulting in a very infrequent and faster counting.
On my index html I use ng-view to insert the other pages by ngRoute. This works fine.
My timer page looks like this:
<div ng-controller="TimeCtrl">
<p>Counter: {{counter.getValue()}}</p>
</div>
Then I wrote a timer controller for my app:
app.controller("TimeCtrl", function($scope, $interval, Counter){
$scope.counter = Counter;
$interval(function(){
$scope.counter.update();
}, 1000);
});
And there's a counter factory:
app.factory("Counter", function () {
this.counter = 0;
var self = this;
return {
update : function () {
self.counter++;
},
getValue : function () {
return self.counter;
}
}
});
I would appreciate any help as Angular does not come that easy for me in the beginning.
Instead of using the $interval on the controller, use it on the service/factory:
app.factory("Counter", function ($interval) {
this.counter = 0;
var self = this;
$interval(function(){
self.update();
}, 1000);
return {
update : function () {
self.counter++;
},
getValue : function () {
return self.counter;
}
}
});
This way if your TimeCtrl gets destroyed (for whatever reason), the counter will still get incremented.
Note also that every time you re-open a page that creates a new TimeCtrl, a new interval will also be defined. That's why you are getting that "infrequent and faster counting".
Regarding it being loaded only on the second page, make sure that you add the Counter service as dependency to your main controller, so that the service gets instantiated immediately (otherwise the counter will not start). If you do not have a main controller, use a run block:
app.run(function(Counter) {
// just inject the Counter so that it gets instantiated
})
Related
// Works
var counter = 0;
var myInterval = Meteor.setInterval(function(){
counter++;
var time = moment().hour(0).minute(0).second(counter).format('HH:mm:ss');
console.log(time);
}, 1000);
// Inside Helper - Does Not Work
Template.clockRunner.helpers({
start: function () {
var counter = 0;
var time = moment().hour(0).minute(0).second(counter).format('HH:mm:ss');
var myInterval = Meteor.setInterval(function(){
counter++
}, 1000);
return time;
},
})
The first version console logs the time in increments of 1 second. The Helper version displays "00:00:00" in the DOM, but does not increment, if I console log the time in helper it logs "00:00:00" every second.
I'm not sure if I'm misunderstanding the reactive nature of helpers or if I'm not seeing a minor mistake. Thanks in advance!
a helper is meant to provide data to a Blaze template; it won't get called unless invoked from a template.
that said, you should think of a helper as something that only provides data, it shouldn't "do anything." as a template renders, and as reactive data is processed, a helper may get called several times in unexpected ways.
i reckon you want your timer to be started in the onRendered() method; that is called once as a template is put on the screen. (there's a corresponding method that's called when the template is taken off the screen, so the timer can be stopped).
once your timer is started, you can write timer data to a reactive variable, and then a helper that returns a formatted version of that timer data. because it's in a reactive var, that will ensure your helper is re-invoked each time the timer ticks.
the last part is simply ensuring the Blaze template references the helper.
The problem is that i need to call a function every single time that a route change to a specific state, lets say i have chatController and i have to fire a() every second but if i exit the controller i have to stop a() and when i'm back to chatController i have to restart a()
My code:
$scope.stop = $interval(yourOperation, 1000);
var dereg = $rootScope.$on('$locationChangeSuccess', function() {
$interval.cancel($scope.stop);
dereg();
});
function yourOperation() {
console.log('$location', $location.$$url)
console.log('test');
}
Works fine executing every single and stops when the controller change, but it doesn't work anymore if i go back, i tried with ng-init() function but only fires the first time that the controller start, i need it always when i'm on a specifict controller.
1] If it is state then you can use following event to call function every time when you back to state
$scope.$on('$ionicView.enter',function(){
$scope.callingFunctionName();
});
Here you may need to add following attribute in app.js state declaration
cache: false
2] In case you are using modal then controller will automatically get initialize.
just need to call function like following -
$scope.callingFunctionName();
hi this code must be in the controller
// .... controller
$scope.stop = $interval(yourOperation, 1000);
function yourOperation() {
console.log('hi')
}
$scope.$on('$destroy',function () {
$interval.cancel($scope.stop);
})
tl;dr: The initial question was "How to trigger a callback every digest cycle?" but the underlying question is much more interesting and since this answers both, I went ahead and modified the title. =)
Context: I'm trying to control when angular has finished compiling the HTML (for SEO prerendering reasons), after resolving all of its dependencies, ngincludes, API calls, etc.
The "smartest" way I have found so far is via checking whether digest cycles have stabilized.So I figured that if I run a callback each time a digest cycle is triggered and hold on to the current time, if no other cycle is triggered within an arbitrary lapse (2000ms), we can consider that the compilation has stabilized and the page is ready to be archived for SEO crawlers.
Progress so far: I figured watching $rootScope.$$phase would do but, while lots of interactions should trigger that watcher, I'm finding it only triggers once, at the very first load.
Here's my code:
app.run(function ($rootScope) {
var lastTimeout;
var off = $rootScope.$watch('$$phase', function (newPhase) {
if (newPhase) {
if (lastTimeout) {
clearTimeout(lastTimeout);
}
lastTimeout = setTimeout(function () {
alert('Page stabilized!');
}, 2000);
}
});
Solution: Added Mr_Mig's solution (kudos) plus some improvements.
app.run(function ($rootScope) {
var lastTimeout;
var off = $rootScope.$watch(function () {
if (lastTimeout) {
clearTimeout(lastTimeout);
}
lastTimeout = setTimeout(function() {
off(); // comment if you want to track every digest stabilization
// custom logic
}, 2000);
});
});
I actually do not know if my advice will answer your question, but you could simply pass a listener to the $watch function which will be called on each iteration:
$rootScope.$watch(function(oldVal, newVal){
// add some logic here which will be called on each digest cycle
});
Have a look here: http://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch
Working on an interesting issue with Angular and frequent updates from SignalR.
on the server, I have a singleton instance of a manager which is currently set to send updates every 500ms
public void StartTest() {
var i = 0;
while (i < 1000) {
if (_cancelRequested) {
_cancelRequested = false;
return;
}
_context.Clients.All.sent(i++);
Thread.Sleep(500);
in my view i'm using "controller as vm" syntax:
{{vm.status}}
finally in my controller (on signalR initialization):
// Using 'Controller As' syntax, so we assign this to the vm variable (for viewmodel).
var vm = this;
vm.status = '';
...
function activateSignal() {
var hub = $.connection.mailerHub;
hub.client.sent = function (counter) {
log(' singal_R ## event: sent ' + counter); // <-- this works on every update
vm.status = counter; // this works only on last update, or if stop is pressed it writes the most recent update.
};
that code does 2 things: writes to log (this sends a toastr update to the browser), and sets the status variable on the controller.
the log comments are reflected in the browser as expected, every half second i get the nice toastr notification with event number, however setting the variable doesn't have any effect, until i request stop, or it reaches 999.
when i press stop, vm.status gets written out with last sequence number, or when the entire thing stops, it writes out 999.
so it appears to be some issue with frequency of setting properties on vm? would i need something like $scope.$watch to make this work?
Well, it is not clear enough for me exactly what you are trying to achieve, but it looks like you are updating your model outside the control of angular. So, you need to notify angular of what you are doing.
Most likely, something like this should solve the problem:
hub.client.sent = function (counter) {
$scope.apply(function(){
log(' singal_R ## event: sent ' + counter);
vm.status = counter;
});
};
I'm not sure what is the correct title for this question, so please correct me if I am wrong.
Let's say that on page refresh (load) I need to perform an animated scrolling to anchor according to the hash of current location (I know about ngAnchorScroll, but I need exectly an animated scolling, which ngAnchorScorll can't to do).
To do that I implemented a service, which will be called on controller initialization:
var Ctrl = function ($scope, initAnchorScrolling) {
initAnchorScrolling($scope);
};
Ctrl.$inject = ['$scope', "initAnchorScrolling"];
applicationServices.factory("initAnchorScrolling",
['scroll', '$location', '$timeout', function(scrollUtils, $location, $timeout) {
return function(scope) {
/** Some stuff here */
function scroll() {
var hash = $location.hash(), elm;
if (!hash) {
scrollUtils.scrollTo(0, 0);
} else if ((elm = getAnchorElement(hash))) {
scrollUtils.scrollToElement(elm);
} else {
scrollUtils.scrollTo(0, 0);
}
}
scope.$watch(function scrollWatch() { return $location.hash(); },
function scrollWatchAction() {
$timeout(function () {
scroll();
}, 0, true);
});
};
}]);
You may see that this implementation of anchors is very similar to implementation of ngAnchorScroll. The main difference is that I'm using jQuery.animate() to move between anchors.
As I understood from here to defer function execution till the moment, when browser finished render, we just need to call $timeout service with a delay parameter, which equals to 0. But this is not working for me. Sporadically page scrolls to the different positions. I can explain it this way - scroll function invoked before digest cycle finished synchronization of views and models, so at the moment of invokation HTML blocks have invalid height and position.
So my question: is it possible to defer function invokation till the moment when browser fully finished render and a digest cycle finished synchronization of views and models (applied bindings)?
The original question is about scrolling to a particular hash while this solution is about retaining the scroll position no matter what. Still it might give you a solution
i created a directive that works on the window scroll ( it could updated to work on any element though )
html usage
<div ng-keep-scroll="service.scrollY">
<!-- list of scrolling things here -->
</div>
where "service.scrollY" MUST be a variable within a service. Services retain their state and values, controllers are recreated every time they load and clear their values so you cant use them to store persistent data. the controller has a scope variable pointing to the service.
directive js
app.directive('ngKeepScroll', function ($timeout) {
return function (scope, element, attrs) {
//load scroll position after everything has rendered
$timeout(function () {
var scrollY = parseInt(scope.$eval(attrs.ngKeepScroll));
$(window).scrollTop(scrollY ? scrollY : 0);
}, 0);
//save scroll position on change
scope.$on("$routeChangeStart", function () {
scope.$eval(attrs.ngKeepScroll + " = " + $(window).scrollTop());
});
}
});