AngularJS + Safari: Force page to scroll to top when pages switch - angularjs

I am having an odd, safari-only scrolling behavior using AngularJS.
Whenever the user flips between pages, the pages are being changed as if they are AJAX. I understand they are in AngualrJS, but the resulting behavior is that the browser does not scroll to top when the user switches pages.
I've tried to force the browser to scroll to top whenever a new controller is being used, but it does not seem to do anything.
I'm running the following JS at the top of every controller:
document.body.scrollTop = document.documentElement.scrollTop = 0;
This is also a Safari-only bug, every other browser will scroll to top when the page changes. Has anyone encountered a similar issue or think of a better way to resolve it?

$window.scrollTo(0,0) will scroll to the top of the page.
I just found a nice plugin (pure angularJS) that supports animations also:
https://github.com/durated/angular-scroll

you can use this:
.run(["$rootScope", "$window", '$location', function($rootScope, $window, $location) {
$rootScope.$on('$routeChangeStart', function(evt, absNewUrl, absOldUrl){
$window.scrollTo(0,0); //scroll to top of page after each route change
}}])
or for tab switches you can use the $window.scrollTo(0,0); in your controller

Have you tried using $anchorScroll()? it's documented here.

I got the same problem while using AngularJS in a Cordova App. In a normal Browser or on Android i have no trouble but on ios i get the same behavior as discribed by Neil.
The AngularJS documentation on $anchorScroll is not that great so i thought to post this link which helped me way more:
http://www.benlesh.com/2013/02/angular-js-scrolling-to-element-by-id.html

You can use $anchorScroll
$scope.gotoTop = function (){
// set the location.hash to the id of
// the element you wish to scroll to.
$location.hash('top');
// call $anchorScroll()
$anchorScroll();
};

Like #nonstopbutton said, adding autoscroll="true" to my ngView element worked for me too. I mention this here because it was a comment to an answer and it was not easy to see his reply.
More information here: https://stackoverflow.com/a/24549173/1578861

I'd a similar issue with Chrome. However, I don't know if any specific external library is causing this issue or otherwise.
However I wrote this piece of code at app level and it works.
$rootScope.$on('$viewContentLoaded', function(){
$window.scrollTo(0, 0);
});

Call $window.scrollTo(0,0); after locationChangeSuccess event:
$rootScope.$on("$locationChangeSuccess",
function(event, current, previous, rejection) {
$window.scrollTo(0,0);
});

In the controller you can actually drop the $ from window and simply putwindow.scrollTo(0,0); without having to inject $window into the controller. It worked great for me.

Related

$location.path change hash to %23 & .hash is not working fine

I want to redirect to the another page & focus should be on the some DIV with id let's say 'some-div-id'.
I tried following
$location.path('/' + $scope.config_path.school + '/' +
$routeParams.someUrl + '/#some-div-id')
This is working fine but it first change # to %23
Like
/%23some-div-id #If '#' is not already present in the URL
/%23some-div-id#some-div-id #If '#' is laready present in the URL
/#some-div-id
I also tried following
$location.path('/' + $scope.config_path.school + '/' +
$routeParams.someUrl + '/').hash('some-div-id')
it is creating a proper URL but not scroll down to the DIV with id some-div-id
EDITED
app.run(function($rootScope, $location, $anchorScroll, $routeParams) {
$rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
setTimeout(function() {
if ($location.hash()) {
$anchorScroll($location.hash());
}
});
});
})
app.controller('MainCntrl', function($scope, $location, $anchorScroll, $routeParams) {
});
Also i tried $location.path & $location.url
Try using $location.url instead of $location.path .
As per the documentation , $location.path only changes the path whereas $location.url changes the path, search and hash.
So the code would be like
$scope.goto = function() {
$location.url('pageone#one');
};
Where pageone is URL of the state and #one is the ID
Also to make it work when directly visiting the URL with the ID in it , use $anchorScroll . On the $stateChangeSuccess event , checking weather $location.hash is present or not.If present , then call $anchorScroll . The code would look like
.run(function($rootScope, $location, $anchorScroll,$timeout) {
$rootScope.$on('$stateChangeSuccess',
function(event, toState, toParams, fromState, fromParams) {
$timeout(function() {
if ($location.hash()) {
$anchorScroll();
}
});
});
})
Example - http://plnkr.co/edit/4kJjiMJImNzwLjRriiVR?p=preview ( For seeing change in URL check http://run.plnkr.co/plunks/4kJjiMJImNzwLjRriiVR/#/pageone )
This behavior is expected. Angular will not allow a second # in your url by default, thus every # beyond the first one will be escaped, resulting in %23 which is the escape character for #: http://www.w3schools.com/tags/ref_urlencode.asp.
You could try enabling html5mode by adding the following to your .config block:
$locationProvider.html5Mode({
enabled: true
});
If that doesn't solve your problem, or if you need to support support IE8, your best bet would probably be to use $anchorScroll().
Here's the documentation.
In your app, you could pass the id of the div you want to scroll to as a parameter in your url (using $routeParams or ui-router's $stateParams), and then call a function that will scroll to that div immediately on page load, such as:
$scope.scrollTo = function(id) {
$location.hash(id);
$anchorScroll();
}
Please also read the following for a more detailed explanation on how to use $anchorScroll() for your specific problem:
How to handle anchor hash linking in AngularJS
Namely, this particular section:
Set up your angular routing as usual, then just add the following code.
app.run(function($rootScope, $location, $anchorScroll, $routeParams) {
//when the route is changed scroll to the proper element.
$rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
$location.hash($routeParams.scrollTo);
$anchorScroll();
});
});
and your link would look like this:
Test/Foo
$location.path(page);
just add the dependency in ur controller by add $location,
This method redirects you on this route, without any error.
In ng-book, there is something about %13 signs replacing normal letter.
Unfortunately, it's about different topic - it is about an $http or xhr api call through REST, where there are two modes ( same would be here ) - one normal mode and second JQuery like. It turned out that jquery adds these %13 signs to things like arr[]=1.
Why i am saying this?
Because after solution comes libraries and functions which support marginal conditions.
The situation in Your question is quite simple. There is $location service, which is solution and urls are created simillary in two modes, which are hashbang and not hashbang.
In problem with jquery parameters, developers provided library for exchanging jquery parameters to normal. There are many converters in real life, like pdf to doc etc.
Please look at short chapter in this book.
It is stated that in html5 mode, you cannot have two hashes in url, but in hashbang mode you can.
Browser cannot know which hash is to go to in hashbang mode. The support for this is anchorScroll service.
I haven't checked if this works in this case but try to configure your app with AnchorScrollProvider:
.config(function($anchorScrollProvider){
$anchorScrollProvider.disableAutoScrolling();
});
Then as i written, you can inject $anchorService service to any place in your app ( especially in controller binded to view containing some-div-id div ) And call anchorScroll() function of this service at any chosen time.
I cannot test it right now so this is theoretical answer.
I had the same problem before and this post helped me out.
Let's hope it is going to help you aswell, if it doesn't let me know so I can help you find another solution.
Good luck.

The event $scope.$on('$destroy') doesn't work updating ionic & angular

I use $interval and need to detect when the controller is destroyed. Until now, I have used the $destroy event and it worked perfectly. For example with this basic code, it prints "destroy" in the console when I go to another page (with a simple <a href="#/myNewUrl"> in myView.html).
angular.module('myModule').controller('myController', ['$scope', function($scope) {
$scope.$on('$destroy', function() {
console.log('destroy');
});
}]);
But since I updated Ionic to the new version (v1.0.0-beta.14), that uses the new version to Angular too (v1.3.6), the $destroy event isn't detect when I go to another page.
Does anybody get the same problem? How can I be resolve it?
Thank you for your answer!
EDIT:
I have finally fixed the problem!!! Now, with the new Ionic version, the view is cached automatically. Adding cache-view="false" in the template disable it.
But I found a best way than the destroy event. Ionic added new events (on $ionicView) and now you can detect when you leave the page (and the page stays cached) with: $ionicView.leave.
To get more information: http://ionicframework.com/docs/nightly/api/directive/ionView/
Is your template cached? If you don't have cache: false in your state routes, then the controller is not destroyed.
http://forum.ionicframework.com/t/how-to-destroy-controllers-in-ion-tab-directive/16658
It's a hello from Ionic dev team. They like to leak memory, you see.
Just set
$ionicConfigProvider.views.maxCache(0);
That should do it

Clear History and Reload Page on Login/Logout Using Ionic Framework

I am new to mobile application development with Ionic. On login and logout I need to reload the page, in order to refresh the data, however, $state.go('mainPage') takes the user back to the view without reloading - the controller behind it is never invoked.
Is there a way to clear history and reload the state in Ionic?
Welcome to the framework! Actually the routing in Ionic is powered by ui-router. You should probably check out this previous SO question to find a couple of different ways to accomplish this.
If you just want to reload the state you can use:
$state.go($state.current, {}, {reload: true});
If you actually want to reload the page (as in, you want to re-bootstrap everything) then you can use:
$window.location.reload(true)
Good luck!
I found that JimTheDev's answer only worked when the state definition had cache:false set. With the view cached, you can do $ionicHistory.clearCache() and then $state.go('app.fooDestinationView') if you're navigating from one state to the one that is cached but needs refreshing.
See my answer here as it requires a simple change to Ionic and I created a pull request: https://stackoverflow.com/a/30224972/756177
The correct answer:
$window.location.reload(true);
I have found a solution which helped me to get it done. Setting cache-view="false" on ion-view tag resolved my problem.
<ion-view cache-view="false" view-title="My Title!">
....
</ion-view>
Reload the page isn't the best approach.
you can handle state change events for reload data without reload the view itself.
read about ionicView life-cycle here:
http://blog.ionic.io/navigating-the-changes/
and handle the event beforeEnter for data reload.
$scope.$on('$ionicView.beforeEnter', function(){
// Any thing you can think of
});
In my case I need to clear just the view and restart the controller. I could get my intention with this snippet:
$ionicHistory.clearCache([$state.current.name]).then(function() {
$state.reload();
});
The cache still working and seems that just the view is cleared.
ionic --version says 1.7.5.
None of the solutions mentioned above worked for a hostname that is different from localhost!
I had to add notify: false to the list of options that I pass to $state.go, to avoid calling Angular change listeners, before $window.location.reload call gets called. Final code looks like:
$state.go('home', {}, {reload: true, notify: false});
>>> EDIT - $timeout might be necessary depending on your browser >>>
$timeout(function () {
$window.location.reload(true);
}, 100);
<<< END OF EDIT <<<
More about this on ui-router reference.
I was trying to do refresh page using angularjs when i saw websites i got confused but no code was working for the code then i got solution for reloading page using
$state.go('path',null,{reload:true});
use this in a function this will work.
I needed to reload the state to make scrollbars work. They did not work when coming through another state - 'registration'. If the app was force closed after registration and opened again, i.e. it went directly to 'home' state, the scrollbars worked. None of the above solutions worked.
When after registration, I replaced:
$state.go("home");
with
window.location = "index.html";
The app reloaded, and the scrollbars worked.
The controller is called only once, and you SHOULD preserve this logic model, what I usually do is:
I create a method $scope.reload(params)
At the beginning of this method I call $ionicLoading.show({template:..}) to show my custom spinner
When may reload process is finished, I can call $ionicLoading.hide() as a callback
Finally, Inside the button REFRESH, I add ng-click = "reload(params)"
The only downside of this solution is that you lose the ionic navigation history system
Hope this helps!
If you want to reload after view change you need to
$state.reload('state',{reload:true});
If you want to make that view the new "root", you can tell ionic that the next view it's gonna be the root
$ionicHistory.nextViewOptions({ historyRoot: true });
$state.go('app.xxx');
return;
If you want to make your controllers reload after each view change
app.config(function ($stateProvider, $urlRouterProvider, $ionicConfigProvider) {
$ionicConfigProvider.views.maxCache(0);
.state(url: '/url', controller: Ctl, templateUrl: 'template.html', cache: false)
cache: false ==> solved my problem !
As pointed out by #ezain reload controllers only when its necessary. Another cleaner way of updating data when changing states rather than reloading the controller is using broadcast events and listening to such events in controllers that need to update data on views.
Example: in your login/logout functions you can do something like so:
$scope.login = function(){
//After login logic then send a broadcast
$rootScope.$broadcast("user-logged-in");
$state.go("mainPage");
};
$scope.logout = function(){
//After logout logic then send a broadcast
$rootScope.$broadcast("user-logged-out");
$state.go("mainPage");
};
Now in your mainPage controller trigger the changes in the view by using the $on function to listen to broadcast within the mainPage Controller like so:
$scope.$on("user-logged-in", function(){
//update mainPage view data here eg. $scope.username = 'John';
});
$scope.$on("user-logged-out", function(){
//update mainPage view data here eg. $scope.username = '';
});
I tried many methods, but found this method is absolutely correct:
$window.location.reload();
Hope this help others stuck for days like me with version: angular 1.5.5, ionic 1.2.4, angular-ui-router 1.0.0

How do I get /mylink#sectionid to work in angularjs?

If I wasn't using angular, then the route mylink would be loaded, then the browser would scroll down to the sectionid section.
In Angular it doesn't scroll. I read some completely crazy whacky solutions involving injecting multiple modules and having crazy unique URLs. I refuse to do things like this.
I want my href values to remain standard. Is there any way in Angular to do this?
Keep in mind, if "mylink" was already loaded, then the links work fine, but if I'm on a different page, say "home", then I navigate to mylink#sectionid, then the scrolling won't occur.
(I mean... if Angular can't do this, I would consider that a bug. It'd be absurd to not support a regularly used syntax since the 90s that is still used today)
EDIT: I think the issue may be the amount of AJAX on this website.
It is certainly possible, you will need to inject in $anchorScroll into your controller
The example from the angular site:
function ScrollCtrl($scope, $location, $anchorScroll) {
$scope.gotoBottom = function (){
// set the location.hash to the id of
// the element you wish to scroll to.
$location.hash('bottom');
// call $anchorScroll()
$anchorScroll();
};
}
From anther route you could handle this via parameter being passed into the route and scroll upon initialization based upon the route param.
I'm not a big fan of my solution, but I listen to onRouteChange, then inject anchorScroll and simply call anchorScroll after a 1000 ms timeout and because the hash is already set nothing more needs to be done. [giving time for all angular stuff to work its self out (the site I'm working on has entirely too much AJAX, but I don't have control of the data yet, so there is nothing I can do about that)]
Anywho, manually initiating anchor scroll works. If anyone knows a better way to do this, that'd be swell.

Add "intermediary" page prior to full page load Angular

My problem
I am using Angular to create a Phonegap application. Most of my pages are fairly small and the transition/responsiveness is quick and smooth. However I have one page that is fairly large that I am having an issue with.
The method for changing to this page is straightforward:
<button ng-click="$location.url('/page2')"></button>
When you "tap" the button above it takes about 1-2s to respond and change pages. I have double checked all areas for improvement on this page and determined that the delay is caused by Angular compiling and parsing the DOM of this page prior to changing the page. Please note that I am testing this on a real device so it is not due to emulator speeds.
The question
Is there a way to automatically or manually intercept page changes and put them in a sort of "loading" page so the response to the button click is immediate and page change is visible but the page content loads in a second or 2 later onto this "loading" page.
Its only an issue cause it is very awkward to click something and have nothing happen. I am having a very hard time finding any resources on this matter so if someone can even point me in the right direction to look I would be grateful.
Edit:
A super hacky solution I found was to use an ng-include on wrapper page and delay the include for a little bit.
myBigPageWrapper.html:
<div ng-include="page"></div>
Controller:
$scope.page = '';
setTimeout(function() { $scope.page='/pages/myBigPage.html'; $scope.$apply(); }, 1000);
Then navigate to your wrapper page instead: $location.url('/myBigPageWrapper')
This is obviously not ideal... But I hope this helps clarify what I am attempting to do.
Page2.html
This is the section that causes the page to slow down, commenting this out makes the page load very quickly. There are 13 pages in the "auditPages" array each containing about 50 lines of html mostly containing form input elements. Quite a bit of logic however it runs great once it is loaded. I am not going to include all the pages as it would be overload.
<div class="page-contents">
<form name="auditPageForm">
<div ng-repeat="(pageKey, pageData) in auditPages " ng-show="currentAuditPage.name==pageData.name">
<audit-form page="pageData">
<ng-include src=" 'partials/audit/auditSections/'+pageData.name+'.html'" onload="isFormValid(pageKey)"></ng-include>
</audit-form>
</div>
</form>
</div>
To sum up my comments above:
Your question was:
Is there a way to automatically or manually intercept page changes and
put them in a sort of "loading" page?
A lot of people asks for this question since Angular doesn't seem to provide a nice handling of a loading transition.
Indeed, the possible nicest solution would have been to "play" with the resolve property of angular's module configuration.
As we know, resolve allows to run some logic before the targeted page is rendered, dealing with a promise. The ideal would be to be able to put a loading page on this targeted page, while the resolve code is running.
So some people have nice ideas like this one:
Nice way to handle loading icon while route is changing
He uses $routeChangeStart event, so the loading icon would happen on the SOURCE page.
I use it and it works well.
Also, there is another way: make use of $http interceptor (like #oori answer above), to have a common code allowing to put a loading icon but...I imagine you don't want the same icon on every kind of http request the page does, it's up to you.
Maybe in the future, a solution would come directly associated to the resolve property.
Angular has $httpProvider.responseInterceptors
// Original by zdam: http://jsfiddle.net/zdam/dBR2r/
angular.module('LoadingService', [])
.config(['$httpProvider', function ($httpProvider) {
$httpProvider.responseInterceptors.push('myHttpInterceptor');
var spinnerFunction = function (data, headersGetter) {
angular.element(document.getElementById('waiting')).css('display','block');
return data;
};
$httpProvider.defaults.transformRequest.push(spinnerFunction);
}])
// register the interceptor as a service, intercepts ALL angular ajax http calls
.factory('myHttpInterceptor', ['$q','$window', function ($q, $window) {
return function (promise) {
return promise.then(function (response) {
angular.element(document.getElementById('waiting')).css('display','none');
return response;
}, function (response) {
angular.element(document.getElementById('waiting')).css('display','none');
return $q.reject(response);
});
};
}])

Resources