how to notify about $interval end in angularjs - angularjs

I want to make custom animation in my web application, and i'm using $interval method to do some visual effects.
And all i want is to know, when that animation ends.
For example
var myInterval = $interval(function(){
//do some staff
}, 10, 20);
How can i get notify about that interval ends? Except $timeout(fn, 200), of course.
And the second question is also about notifying, but in case, when i cancel that interval is other place manually by $interval.cancel(myInterval), can i get notified about that?

For the first case, you can just do:
myInterval.then(function () { console.log("Finished."); } );
It isn't a good idea to count the number of iterations when Angular is already doing that for you.
See https://docs.angularjs.org/api/ng/service/$q

You can broadcast an event by yourself when canceling the interval. Take a look at $rootScope.$broadcast().
Where you cancel the interval:
$rootScope.$broadcast('Custom::Event');
Where you want to retrieve the broadcast:
$scope.$on('Custom::Event', function (e) { ... });
Edit: Iterations
If you want to send the broadcast after the last iteration, check the first parameter provided to the function for the $interval.
$interval(function (iteration) {
// Do some stuff...
// Broadcast after last iteration
if (iteration === iterations - 1) {
$scope.$broadcast('Interval::Finished');
}
}, delay, iterations);
See: JS Bin

Related

How to invoke a function at a trigger point?

When a trigger point is reached, I just want to call a function or run a few statements to do something, which has nothing to do with interface. Here is how I am doing now for this purpose:
var scene = new ScrollMagic.Scene({triggerElement: "#some-trigger", triggerHook: 'onLeave', offset: })
.on('start', function () {
// statements go here
}).addTo(controller);
Is this the correct way?
The reason to ask this question is that I find out Scroll Magic can be a UNIQUE event control tool.
Little later but I jump to that question because I had the same issue in some projects.
So i solved in two ways:
Using enter or progress event
ENTER is trigger when a scene is starting. It's depends on triggerHook
scene.on("enter", function (event) {
console.log("Scene entered.");
});
PROGRESS is trigger every time
scene.on("progress", function (ev) {
if( ev.progress > 0.25){
//== going farward
}
if(ev.progress < 0.25){
//== going backward
}
})

$watch doesn't work and is driving me mad

So, what I basically want to have is visualisation of the socket connection for the user. Meaning an indication wheter the client is connected to the server over socket.io or not.
Therefor I have service which keeps track of the socket state:
[...]
.factory('DataConnection', function(CONN_EVENTS, socket, $rootScope) {
var dataConnection = {};
dataConnection.states={
// indicates if the user can interact with the ui
uiEnabled: false,
[...]
};
// socket IO connection states
$rootScope.$on(CONN_EVENTS.socketAuth, function() {
dataConnection.states.uiEnabled = true;
});
$rootScope.$on(CONN_EVENTS.socketUnAuth, function() {
dataConnection.states.uiEnabled = false;
});
$rootScope.$on(CONN_EVENTS.socketConnLost, function() {
dataConnection.states.uiEnabled = false;
});
}
which sets the states based on events. This events are emitted in a different service/factory which handles the socket events.
I tried many approaches to sync these values with my controller. It kinda worked with a manual $scope.$apply(), but sometimes it gave me the apply already in progress error and since its very bad practice, I decided to not use it.
I ended up with this solution in my controller:
.controller("MainCtrl", function($scope, DataConnection) {
$scope.$watch(function() {
return DataConnection.states.uiEnabled
}, function(uiEnabled) {
$scope.uiEnabled = uiEnabled;
}, true);
Which doesn't want to work somehow. When the socket get disconnected (the server gets shut down), the ui won't update. After interaction (e.g. trigger a popover), the ui will update as expected.
Is there anything I'm missing? Or something else I can try?
OK this is not a solution to get $watch working but instead just a workaround that "might" get your first approach using events to work without throwing a digest already in progress error. So instead of using $scope.$apply() enclose changes you make inside $timeout(function()) instead like this
$timeout(function(){
// do whatever changes you want here
}) // no timeout value
NOTE that there is no timeout actually added above. Why i think this might work is because a $timeout automatically applies whatever changes you made once script inside completes execution (ie, its like a $scope.$spply() that won't throw a digest cycle already in progress error). I am not sure about this but just give it a try.
I'd simply suggest you to use $scope.$applyAsync(), which means that if an apply in progress, it will do it on the next $digest only.
To me is it OK your approach, to watch a function that returns an object to be observed, I don't know what could be the problem, but I would use callbacks:
.factory('DataConnection', function(CONN_EVENTS, socket, $rootScope) {
var dataConnection = {};
dataConnection.states={
_uiEnabledBackingField: false,
// indicates if the user can interact with the ui
uiEnabled: function(val) {
if (typeof val !== 'undefined') {
this._uiEnabledBackingField = val;
this.callback(val);
return;
}
return this._uiEnabledBackingField;
},
onUiEnabled: function(cb){
if (cb) {
this.callback = cb;
}
}
};
});
And then in my controller:
.controller("MainCtrl", function($scope, DataConnection) {
dataConnection.states.onUiEnabled(function(uiEnabled){
$scope.uiEnabled = uiEnabled;
});
});
I did some research and this is my conclusion:
The $apply will trigger in three different cases (Exploring Angular 1.3: Go fast with $applyAsync) In my case (where I have to update the DOM after a socket event), the $apply has to be triggered manually.
This example is also found in the Angular Wiki:
If you're creating an AngularJS service (such as for sockets) it should have a $scope.$apply() anywhere it fires a callback.
I tried to trigger the $apply in the socket service already (instead of the controller), which worked way better and didn't give me the "$apply already in progress error".

$timeout.flush during protractor test [duplicate]

I'm testing my angular application with Protractor.
Once the user is logged in to my app, I set a $timeout to do some job in one hour (so if the user was logged-in in 13:00, the $timeout will run at 14:00).
I keep getting these failures:
"Timed out waiting for Protractor to synchronize with the page after 20 seconds. Please see https://github.com/angular/protractor/blob/master/docs/faq.md. The following tasks were pending: - $timeout: function onTimeoutDone(){....."
I've read this timeouts page: https://github.com/angular/protractor/blob/master/docs/timeouts.md
so I understand Protractor waits till the page is fully loaded which means he's waiting for the $timeout to complete...
How can I make Protractor NOT wait for that $timeout?
I don't want to use:
browser.ignoreSynchronization = true;
Because then my tests will fail for other reasons (other angular components still needs the time to load...)
The solution will be to flush active timeouts (as #MBielski mentioned it in comments), but original flush method itself is available only in anuglar-mocks. To use angular-mocks directly you will have to include it on the page as a <script> tag and also you'll have to deal with all overrides it creates, it produces a lot of side effects. I was able to re-create flush without using angular-mocks by listening to any timeouts that get created and then reseting them on demand.
For example, if you have a timeout in your Angular app:
$timeout(function () {
alert('Hello World');
}, 10000); // say hello in 10 sec
The test will look like:
it('should reset timeouts', function () {
browser.addMockModule('e2eFlushTimeouts', function () {
angular
.module('e2eFlushTimeouts', [])
.run(function ($browser) {
// store all created timeouts
var timeouts = [];
// listen to all timeouts created by overriding
// a method responsible for that
var originalDefer = $browser.defer;
$browser.defer = function (fn, delay) {
// originally it returns timeout id
var timeoutId = originalDefer.apply($browser, arguments);
// store it to be able to remove it later
timeouts.push({ id: timeoutId, delay: delay });
// preserve original behavior
return timeoutId;
};
// compatibility with original method
$browser.defer.cancel = originalDefer.cancel;
// create a global method to flush timeouts greater than #delay
// call it using browser.executeScript()
window.e2eFlushTimeouts = function (delay) {
timeouts.forEach(function (timeout) {
if (timeout.delay >= delay) {
$browser.defer.cancel(timeout.id);
}
});
};
});
});
browser.get('example.com');
// do test stuff
browser.executeScript(function () {
// flush everything that has a delay more that 6 sec
window.e2eFlushTimeouts(6000);
});
expect(something).toBe(true);
});
It's kinda experimental, I am not sure if it will work for your case. This code can also be simplified by moving browser.addMockModule to a separate node.js module. Also there may be problems if you'd want to remove short timeouts (like 100ms), it can cancel currently running Angular processes, therefore the test will break.
The solution is to use interceptors and modify the http request which is getting timeout and set custom timeout to some milliseconds(your desired) to that http request so that after sometime long running http request will get closed(because of new timeout) and then you can test immediate response.
This is working well and promising.

lodash debounce not-working (I use it wrongly?), function called multiple times

UPDATE: Answered by myself. Scroll to the end.
My objetive is simple:
I have a google map canvas. User can drag the map, and the program automatically downloads the incidents around the center of the map (and draw the markers).
I have this listener for plugin.google.maps.event.CAMERA_CHANGE events. The thing is, this listener triggers multiple times. Meaning: from the time you tap your finger on the canvas -> dragging in accros the canvas -> to the time you lift your finger ... the event triggers multiple times. Not just when you lift your finger.
Apparently it has a watcher that triggers every N miliseconds.
I dont't want my code to perform that costly downloading data from the server + drawing markers during those interim camera_changes. I want to do it only after the user stop dragging. That means: on the last camera change event received during 5 seconds (I figured that the slowest user takes 5 seconds to drag from corner to the opposite corner of the canvas).
Obviouly I turn to debounce for this need. But it doesn't seem to work. I can see from the logs (X1, X2).... that the function gets called multiple times (around 3-to-4 times, depends on how fast you drag across the canvas).
Indeed, they get called only after I stop dragging. But, they get called in series. All 3-4 of them. With delay of 5 seconds between invocation.
That's not what I expected. I also added invocation to the .cancel method (which I think is superfluous..., because if I understand it correctly, the debounce should've already handled that; cancelling interim-invocations within the timeout).
I also tried throttle (which I think conceptually is not the answer. Debounce should be the answer). Anyway, same issue with throttle.
So, my question: where did I do wrong (in using lodash's debounce)?
Thanks!
var currentPosition = initialPosition();
drawMarkersAroundCenter(map, currentPosition);
var reactOnCameraChanged = function(camera) {
console.log('X1');
console.log('-----');
console.log(JSON.stringify(camera.target));
console.log(JSON.stringify(currentPosition));
console.log('-----');
if (camera.target.lat == currentPosition.lat && camera.target.lng == currentPosition.lng) {
return;
}
currentPosition = camera.target;
drawMarkersAroundCenter(map, currentPosition);
}
var debouncedReactOnCameraChange = lodash.debounce(reactOnCameraChanged, 5000, {
'leading': false,
'trailing': true
});
map.on(plugin.google.maps.event.CAMERA_CHANGE, function(camera) {
debouncedReactOnCameraChange.cancel();
debouncedReactOnCameraChange(camera);
});
--- UPDATE ---
I tried a very simplified scenario of using debounce on nodejs console, it works as I expected. I don't even invoke .cancel in the code below. So what's wrong with the above code? I can't see any difference with this simplified code in the image below.
UPDATE
I tried with this dude method instead of "reactOnCameraChanged":
var dude = function(camera) {
console.log('dude');
}
var debouncedReactOnCameraChange = lodash.debounce(dude, 5000, {
'leading': false,
'trailing': true
});
And I also removed the invocation to .cancel:
map.on(plugin.google.maps.event.CAMERA_CHANGE, function(camera) {
//debouncedReactOnCameraChange.cancel();
debouncedReactOnCameraChange(camera);
});
I can see the 'dude' gets printed only once during those 5 seconds.... So.., something that I do inside reactOnCameraChanged is causing interference ... somehow....
RESOLVED:
See answer below.
This code works:
var currentPosition = latLng;
drawMarkersAroundCenter(map, currentPosition);
var debouncedReactOnCameraChange = lodash.debounce(function(camera) {
console.log('reactOnCameraChanged: ' + JSON.stringify(currentPosition));
drawMarkersAroundCenter(map, currentPosition);
}, 3000, {
'leading': false,
'trailing': true
});
map.on(plugin.google.maps.event.CAMERA_CHANGE, function(camera) {
console.log('CAMERA_CHANGE');
if (camera.target.lat == currentPosition.lat && camera.target.lng == currentPosition.lng) {
console.log('same camera spot');
return;
}
console.log('different camera spot');
currentPosition = camera.target;
debouncedReactOnCameraChange(camera);
});

asynchronous calls inside for loop angularJs

I'm trying to make a call to a function 'submittoServer' which is inside factory 'pService', which makes $http call and then broadcast the data. The call to the 'submittoserver' is happening inside a for loop. Problem here is that I couldn't see the actual call is being made until the last loop, which is sending only the last item, but as you see in the code below I want to update one particular variable after every call, can someone please suggest how can I don't that. I can't do call back here as I've other method that call this same factory function with different inputs.
for (var i = vr.lines.length - 1; i >= 0; i--) {
if (parseInt(vr.lines[i].id) === id && Boolean(vr.lines[i].IsVoided) != true) {
lineId = vr.lines[i].lineID;
pService.submitToServer(actionId, { "IData": id }, ineId)
linesRemoved = linesRemoved + 1;
}
if (linesRemoved === lineqty)
{
updateModel = true;
}
}
The problem here is that your service is a promise to return data. Your function will keep looping and running before the promise is resolved. You need to refactor your loop to take this into account.
Either add a .then(fn(){}) to handle the resolve promise. Gather up all the changed lineIds and submit them all at once and (again) handle the resolved promise with a .then(fn(){}). Lastly, given you next set of code logic, you probably want to do something more like $q.all to wait on all promise(s) to resolve before moving forward (see Wait for all promises to resolve)
Example
before your for loop:
var self=this;
self.linesRemoved = 0; // or init as needed.
Inside your for loop.
pService.submitToServer(actionId,{data}).then(function(resp){
self.linesRemoved++; // shortcut, this does +1 to itself.
});
Why do you have update model? With Angular your data is two-way bound and should just react to it being changed.
Sample $http call with return in a service, this is a promise itself:
return $http.post(url, data, { cache: true });
Use this in a controller like
service.callHttp(data).success(function(resp){
self.linesRemoved++;
}).error(function(resp){});
If you have to wait it might be better to hold all and wait until they are all finished.
var promises =[];
for(){
promises.push(service.callHttp());
}
$q.all(promises).then(function(){
// do work if(self.linesRemoved==lineQty)
// update... You can't evaluate until they are all finished right?
});

Resources