Getting the updated $scope in a $timeout in ui-router - angularjs

This question might best be served by showing 2 JSFiddles examples (issue occurs in the 2nd JSFiddle link).
So I was playing with an example in Angular where I go through questions in my app, and when you get to a certain question called 'QS_SEARCH', then it autosubmits the question after 3 seconds.
I added some simple functionality so pretend now you are on the last question 'QS_SUCCESS' and if you clicked back to the 'QS_SEARCH' question, then the autosubmit timeout now gets initiated again... but if you click Back one more time quickly before the $timeout func runs, then when function executes it realizes the user isn't on 'QS_SEARCH' anymore and doesn't autosubmit. This is working correctly.
http://jsfiddle.net/armyofda12mnkeys/wxLqv4cs/
Now the same example with ui-router seems to hold the old $scope value and will still autosubmit even if you clicked Back to 'QS3' pretend, which is incorrect.
How can the $timeout get the updated scope so it doesn't autosubmit when you aren't on that specific question? I even tried $scope.$apply() inside the $timeout function but it still doesn't get the latest value.
http://jsfiddle.net/armyofda12mnkeys/qrp6cjgv/
Is this a Closure issue? I thought though $scope would be updated though.
Here is code for the $timeout:
if($scope.currentquestion.qid == 'QS_SEARCH') {
console.log('TIMEOUT: FUNC WILL START IN 3s');
$timeout(function(){
//$scope.$apply(); //update current question, not working though
console.log('3S DONE: FUNC EXECUTING');
console.log('current question is:'+ $scope.currentquestion.qid);
if( $scope.currentquestion.qid=='QS_SEARCH' ) {//double-check to make sure stilll on this question!!! (where issue occurs)
console.log('STILL on QS_SEARCH so autosubmit!');
$scope.getnextquestion();
}
}, 3000);
}

Related

Waiting for the click event in PhantomJS 2 with Jasmine 2.x and AngularJS

I am trying to test an Angular directive that uses the click event and getting nowhere. I can get the event to fire just fine, but PhantomJS2 just takes forever to make the event happen and I can't figure out a way to get Jasmine to wait for it to happen before running the assertion. I've searched quite a bit and the most common response is this: How to wait for a click() event to load in phantomjs before continuing?. Sadly, that guy hasn't figured it out either, and I'm not waiting for a page to load, just for the click event to finally register. I'm hoping that someone else has and just didn't see that question or didn't want to start a reply on such an old question. For reference, the directive in question selects the text within an input tag if there is none already selected. I've tried every permutation of timeouts that I can think of, but nothing works. The last attempt involved using the done() function from Jasmine, but still no luck.
//the default value in the input is "unchanged"
it('should not select anything if there is already a selection in the element', function(done) {
element[0].setSelectionRange(2, element[0].value.length);
element.triggerHandler('click');
scope.$digest();
expect(theWindow.getSelection().toString()).toEqual('changed');
done();
});
All reasonable suggestions will be considered. I'd be ecstatic if someone had a link to a solution that I somehow didn't find with my Google-foo.
Note: I got the idea about using setTimeout from here: https://newspaint.wordpress.com/2013/03/19/how-to-click-on-a-div-or-span-using-phantomjs/

Why is $locationChangeStart fired upon page load?

Recently I've stumbled upon a very strange code in production that is seemingly using the fact that under some conditions Angular may fire the $locationChangeStart event upon the initial page load. Moreover the next parameter value will be equal to the current value. That seems very odd to me.
I didn't find any relevant documentation for that but here is the fiddle that shows such a situation http://jsfiddle.net/tJSPt/327/
Probably the only difference is that in production we are using the manual Angular bootstrap.
Can anyone explain or point to the trustful sources of information on why is that event triggered upon the page load? Is that something we have to expect or that is just the particularity of the current Angular implementation or our way of using it?
I have experienced this recently but the reason it happened was because I'm using ui-router and the controllerAs syntax. Perhaps you are too?
I stumbled upon this link that helped me out: History should not be changed until after route resolvers have completed
I listened to the $locationChangeStart broadcast but it hit the breakpoint when I entered the state instead of when exciting.
I fixed mine by doing the following:
I listened to $stateChangeStart instead.
I had to move the code above var vm = this;
Here's my code look like after:
// ...
$scope.$on('$stateChangeStart', function (event) {
if (vm.myForm!= null && vm.myForm.$dirty) {
if (!confirm("Are you sure you want to leave this page?")) {
event.preventDefault();
}
}
});
var vm = this;
// vm.xxx = xxxx; .etc ...

$location.search is not updated imediatly

I have a problem that my url doesn't get updated every time i set it. It is somehow connected with the directive, because in other cases it works.
So my question is, on what is the $location.search('dd', val) depended on, what is it waiting for, because function gets called, but the url is not updated.
it waits for a digest..
$scope.$apply(function() {
$location.search('dd', val)
})
Digest cycle takes time to update the DOM.
I had similar issue where I had to reload the page after updating the url.
I used $timeout with code that is required to run after completing the digest cycle.
$location.search('filter', null);
$timeout(function() {
//the code which needs to run after dom rendering
$window.location.reload();
}.bind(this))

Binding not updating, ocModal

I'm using the ocModal directive + service (https://github.com/ocombe/ocModal). When someone wants to delete a record they click the delete button which pops up, via ocModal, a modal to confirm.
oc-modal-close ng-click="deleteNote(id)"
So far so good. Within the controller deleteNote runs. Console.log shows the argument is the id I passed. I call an API to delete the note from my database and on a success call a function to delete the note from the Angular variable $scope.notes.
$scope.removeNote = function(id){
console.log(id);
console.log($scope.notes);
delete $scope.notes[id];
console.log($scope.notes);
};
The id is correct and the $scope.notes after the delete command shows it was correctly deleted. Yet, there is no corresponding update on my front-end. Following advise I've seen on Stackover I used apply() which led to a 'digest cycle already in progress' error. I then tried to use $timeout but while that got rid of the digest cycle error, it didn't solve the problem of the bind.
Is it relevant that the $scope.notes is used by a ng-repeat that then uses a directive? I've experimented a bit and don't think so, but just in case wanted to mention it.
Where are you calling $scope.removeNote from? Is it from your main controller or in the modal's controller? maybe a directive?
It feels like it is hitting the wrong scope. You can try to move notes to $scope.model.notes and see if it helps, as it will ensure the correct scope is referenced, but it's hard to say unless you try and provide a fiddle with the issue occurring.
I've created this simple fiddle trying to mimic an API call with $timeout and it works -> http://jsfiddle.net/7eqsc/1/
angular.module('myApp',[])
.controller('myCtrl',function($scope,$timeout){
$scope.notes={
a:'AngularJS',
b:'Rocks'
}
$scope.addRandom=function(){
$scope.notes[parseInt(Math.random()*10000).toString(36)]='New Item';
}
$scope.removeNote=function(id){
//emulate API call
$timeout(function(){
delete $scope.notes[id];
},2000);
}
});
In general, I recommend placing things under an object, such as ".model.yourArray", it ensures references are kept correctly and will save you a lot of trouble.

How come Angular doesn't update with scope here?

I'm pretty new to Angular and I'm using firebase as my backend. I was hoping someone could debug this issue. When I first go to my page www.mywebsite.com/#defaultHash the data doesn't load into the DOM, it does after visiting another hash link and coming back though.
My controller is like this:
/* initialize data */
var fb = new Firebase('https://asdf.firebaseio.com/');
/* set data to automatically update on change */
fb.on('value', function(snapshot) {
var data = snapshot.val();
$scope.propertyConfiguration = data.products;
console.log($scope.propertyConfiguration);
console.log("Data retrieved");
});
/* save data on button submit */
$scope.saveConfigs = function(){
var setFBref = new Firebase('https://asdf.firebaseio.com/products');
setFBref.update($scope.propertyConfiguration);
console.log("configurations saved!");
};
I have 3 hash routes say "Shared", "Registration", and "Home" with otherwise.redirectTo set to "Shared".(They all use this controller) Here's the error that occurs: (all "links" are href="#hashWhereever")
1) Go to website.com/#Shared or just refresh. Console logs $scope.propertyConfiguration and "Data Retrieved". DOM shows nothing.
2) Click to website.com/#Registration, console logs $scope data properly, DOM is loaded correctly.
3) Click back to website.com/#Shared, console logs $scope data properly yet this time DOM loads correctly.
4) Refresh currently correctly loaded website.com/#Shared. DOM elements disappear.
Since $scope.data is correct in all the cases here, shouldn't Angular make sure the DOM reflects the model properly? Why is it that the DOM loads correctly only when I am clicking to the page from another link.
I can "fix" it by adding window.location.hash = "Shared" but it throws a huge amount of errors in the console.
FIXED:(sorta)
The function $scope.$apply() forces the view to sync with the model. I'd answer this question myself and close it but I'm still wondering why the view doesn't load correctly when I correctly assign a value to $scope. If Angular's "dirty checking" checks whenever there is a possibility the model has changed, doesn't assigning a value to $scope overqualify?
Angular has no way to know you've assigned a value to $scope.variable. There's no magic here. When you run a directive (ng-click/ng-submit) or Angular internal functions, they all call $apply() and trigger a digest (a check of the dirty flags and update routine).
A possibly safer approach than $apply would be to use $timeout. Currently, if you call a write op in Firebase, it could synchronously trigger an event listener (child_added, child_changed, value, etc). This could cause you to call $apply while still within a $apply scope. If you do this, an Error is thrown. $timeout bypasses this.
See this SO Question for a bit more on the topic of digest and $timeout.
This doc in the Angular Developer Guide covers how compile works; very great background read for any serious Angular dev.
Also, you can save yourself a good deal of energy by using the official Firebase bindings for Angular, which already take all of these implementation details into account.
Vaguely Related Note: In the not-too-distant future, Angular will be able to take advantage of Object.observe magic to handle these updates.

Resources