Why is $locationChangeStart fired upon page load? - angularjs

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 ...

Related

Angularjs UI-Router state change changes functionality of Google-API directive

I am using a google places autocomplete directive: vs-google-autocomplete
which works fine. The problem is, if we change the $state (by back / forward button, or just ui-sref="testroute", or $state.go()), it suddenly does not work the way it worked before.
I set up a plnkr:
google.maps.event.addListener(autocomplete, 'place_changed', function() {
console.log("place_changed");
});
google.maps.event.addDomListener(element[0], 'keydown', function(event) {
if (event.keyCode == 13 || event.keyCode == 9)
console.log("keydown");
});
http://plnkr.co/edit/wbhEWds1pY1bWrguV40U?p=preview
You can enter an address and just hit enter. Then open the console. You will see:
keydown
place_changed
but if you now click on route2 and then back on route1, enter another address and press the enter button, console will log:
place_changed
keydown
The order of the eventhandlers changed. It seems that google search / - functionality is somehow broken, when google is loaded once, then the $state changes and you again try to use google.
Also, there is a second .pac container in the dom, and I guess google is somehow loaded twice or something while the controller just reloads.
Do you know why this behaviour happens and how I can prevent that or do you have at least an idea on how to workaround this problem? I need keydown to be before place_changed. The google search still works in the plnkr, but it is buggy a bit. I think it is a problem with angularjs/$state loading the controller / view and the directive or something.
//edit:
I guess it is not really a problem with the vs-google directive nor google itself, maybe just they way of them being initiated. Vs-google takes the element of the directive and initializes its code. Because google-API is included in the index.html file and a directive loads on state change, it seems that it reinitializes the DOM element or something, but still, the event is just called once

dynamic header/menu in angularjs

While transitioning an existing angular site, I encountered an annoying problem. The initial symptom was that a certain controller was not running it's initialize function immediately following the login. I logged and I tracked, and eventually I realized it was a design flaw of the page. Essentially, index.html contains a <header>, <ng-view>, and <footer>. There are a couple of ng-if attributes that live in the header that I want to evaluate after the login, but since the view is the only thing that is reloaded, it was not reinitializing the header controller, and thus not updating the ng-if values.
Then I was reminded of ngInclude, which seems like the perfect solution, until I got it hooked up and realize that doesn't work either. It loads the template the first time, and doesn't reinitialize when the view changes. So then I got the bright idea of passing the HeaderController to another controller or service, and controlling this one stubborn boolean value through a proxy of sorts. That also didn't work. Then I tried putting a function and a boolean into another service, and mirroring that property in the header controller, but thus far I have not gotten this working.
I have done plenty of research about multiple views in the index, and so far I hear a lot about this ui-router, but I'm still not convinced that is the way I want to go. It does not seem to be a simple solution. I have not tried putting the ng-include into the templates yet either, because then I feel like that is going back in time to when we had to update 100 pages every time we changed the menu.
I lost a whole day to this. If anyone could tell me how to trigger the evaluation of this one property in my header controller which I would like to live outside the other templates, please let me know!
Ok so you need to know in your HeaderController when the view has reloaded. There's a number of ways of doing this but the easier and maybe the more correct in this particular case is with an event.
So when you are refreshing the view you just do this, let's say you need the new value of ob1 and ob2 variables.
// ViewController
$rootScope.$emit('viewRefresh', {ob1: 'newvalue1', ob2: 'newvalue2'});
And in your HeaderController you need to listen for that event, and set on your $scope the new values for those attrs (if you're not using controller as syntax).
// HeaderController
$rootScope.$on('viewRefresh', function onRefresh(event, data) {
$scope.ob1 = data.ob1;
$scope.ob2 = data.ob2;
})
Another Solution
Sharing a Promise through a Service (using $q)
function HeaderService($q) {
var defer = $q.defer();
return {
getPromise: function() {return defer.promise},
notify: function(data) {defer.notify(data)}
}
}
function HeaderController(HeaderService) {
var vm = this;
HeaderService.getPromise().then(function(data) {
vm.ob1 = data.ob1;
vm.ob2 = data.ob2;
})
}
function ViewController(HeaderService) {
var data = {ob1: 'newvalue1', ob2: 'newvalue2'};
HeaderService.notify(data)
}

Post-load event Angularstrap modal

I'm using angular chosen with angularstrap and i'm having problems with the initial value of the selector to be selected. The way i got it to work is i set a Timeout on the model attached to the selector to wait for the dom and then set the model value. So my guess is that chosen needs to wait for the dom to be created before it can initialize the selected option.
$scope.showModal = function() {
myModal.$promise.then(myModal.show);
// hack to make chosen load
$timeout(function () {
myModal.$scope.SelectedColor = "green";
}, 500 );
};
in my opinion this timeout solution is not a good one and i would like to find a better way to set the model after the dom has been created.
This is because chosen directive is calling trigger("chosen:updated") before the DOM is actually loaded. A fix would be adding $timeout() to the $watchCollection trigger.
This has been discussed and looks like the solution is here in the answer from kirliam.
Someone should issue a pull request for this issue.
edit: I issued a pull request for a fix regarding this issue. Hope it gets merged in.

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.

In backbone marionette is there a way to tell if a view is already shown in a region?

Given something like this:
View = Backbone.Marionette.ItemView.extend({ });
myView = new View();
//region already exists
myLayout.region.show(myView)
//some time later this gets called again:
myLayout.region.show(myView)
I can see currentView in the docs but this only seems to apply at initialisation. Once a view is shown can I query the region to see the view? Either the view instance or type would be helpful. Looking in Chrome's debugger I can't see any properties/methods on the region that would help.
The motive for wanting to do this is so I don't show a static item view in a region again if it is already displayed as this can (especially if images are involved) cause a slight flickering effect on the screen.
Thanks
--Justin Wyllie
you can add a condition before calling show method:
if (myLayout.region.currentView != myView)
myLayout.region.show(myView)
so if you'll try to call show with the same View it wont be shown.
if you want to call region.show(myView) once you can check in this way:
if (_.isUndefined(myLayout.region.currentView))
myLayout.region.show(myView)
You can check the isClosed and $el attributes of the view. Something like
if (myView.isClosed || _.isUndefined(myView.$el)) {
myLayout.region.show(myView);
}
This is the same way the region checks to see if the view is closed or not:
show: function(view) {
this.ensureEl();
var isViewClosed = view.isClosed || _.isUndefined(view.$el);
...
I'm going out on a limb here and assuming that the OP's question is based on app behavior when navigating to different parts of the app via an anchor tag in the navigation or something similar.
This is how I found the question and I thought briefly that the answers would save my day. Although both answers so far are correct they do not quite solve the problem I was having. I wanted to display a persistent navigation bar. However, I did not want it to display on the login page. I was hopeful that detecting if a Region was already shown or not I'd be able to properly let the display logic take care of this.
As it turns out we were both on the right track to implement Regions as this provides granular control, but even after implementing the above I found that my nav bar would still "flicker" and essentially completely reload itself.
The answer is actually a bit ridiculous. Somehow in all the Backbone tutorials and research I've been doing the last two weeks I never came across the need to implement a javascript interface to interrupt normal link behavior. Whenever a navigation item was clicked the entire app was reloading. The routing was functioning so the content was correct, but the flicker was maddening.
I added the following to my app.js file right after the Backbone.history.start({pushState: true}); code:
// Holy crap this is SOOO important!
$(document).on("click", "a[href^='/']", function(event) {
if (!event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
event.preventDefault();
var url = $(event.currentTarget).attr("href").replace(/^\//, "");
Backbone.history.navigate(url, { trigger: true });
}
});
Check out this article for some explanation about the keyPress detection stuff. http://dev.tenfarms.com/posts/proper-link-handling
Boom! After adding this stuff in my app no longer completely reloads!
Disclaimer: I am very new to Backbone and the fact that the above was such a revelation for me makes me think that I may be doing something wrong elsewhere and this behavior should already exist in Backbone. If I've made a giant error here please comment and help me correct it.

Resources