Autoscroll to TOP with ui-router and Angularjs - javascript

I've read so many different problems with this and none of the solution given seem to fit my use case. I started by simply putting target="_top" on all my links, but that actually forces my app to reload which wont work. I've also seen people say they use autoscroll="true" but that only seems to work if its within my ui-view.
The issue with this is that in my index.html file I have fixed nav and other static elements that are above my first ui-view. This means when I go to other pages I lose the navigation as the page loads past those elements. I've also tried putting this on the body with:
<body autoscroll="true">
</body>
This doesn't seem to do anything either. So the question is, how can I make sure that new pages (new route changes from ui-router) result in starting at the top of the page? THANKS!

If you want it to always scroll to 0 cross-browser, do nothing with autoscroll. Just place this your run block:
$rootScope.$on('$stateChangeSuccess', function() {
document.body.scrollTop = document.documentElement.scrollTop = 0;
});

Using version 1.0.6, the $stateChangeSuccess event is deprecated in favor of the $transitions service. Here is the code I used to scroll to the top on every state change:
app.run(['$transitions', function ($transitions) {
$transitions.onSuccess({}, function () {
document.body.scrollTop = document.documentElement.scrollTop = 0;
})
}]);

I had exactly the same problem, fixed navbar on route changes, page loading partially scrolled down the page.
I just added autoscroll="false" to ui-view, like so:
<div ui-view="main" autoscroll="false"></div>
edit
Just tested this method, bit of a dirty hack, but it works. Import angular services $anchorScroll & $location into the relevant controllers for ui-router .state config. Then use a $watch on ui-router $stateParams to call $location.hash('top'); on route/state changes.
https://docs.angularjs.org/api/ng/service/$anchorScroll
https://docs.angularjs.org/api/ng/service/$location#hash
.controller('myCtrl', function ($location, $anchorScroll, $scope, $stateParams) {
$scope.$watchCollection('$stateParams', function() {
$location.hash('top');
$anchorScroll();
});
});

There is an Angular service for this.
https://docs.angularjs.org/api/ng/service/$anchorScroll
Sample Code:
.run(function ($rootScope, $state, $stateParams, $anchorScroll) {
$rootScope.$on('$stateChangeStart', function () {
$anchorScroll();
});
});
If you want to scroll to a specific element
.run(function ($rootScope, $state, $stateParams, $anchorScroll) {
$rootScope.$on('$stateChangeStart', function () {
// set the location.hash to the id of
// the element you wish to scroll to.
$location.hash('bottom');
// call $anchorScroll()
$anchorScroll();
});
});

I am using ui-router. My run looked like this:
.run(function($rootScope, $state, $stateParams){
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
$rootScope.$on('$stateChangeSuccess', function() {
document.body.scrollTop = document.documentElement.scrollTop = 0;
});
})

I had to do a combination of the other two answers.
<div ui-view autoscroll="false"></div>
In combination with
$rootScope.$on('$stateChangeSuccess', function() {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
});
Note: This is on v0.2.15 of ui-router

Did the same thing as the excepted answer from #TaylorMac but in $locationChangeStart.
index.run.js:
$rootScope.$on('$locationChangeStart', function() {
document.body.scrollTop = document.documentElement.scrollTop = 0;
});

Related

Waypoints.js not working as expected with angular views

I am trying to apply some css animation to some of the elements in my views when they reach the top of the window (offset 70%) with the help of waypoints.js.
I've created a directive as shown below.
angular.module("myApp")
.directive("clWaypoints",function(){
return{
restrict:"A",
link: function(scope,element,attrs)
{
var wayp=new Waypoint({
element:element,
handler:function(direction){
if(direction=="down")
{
var animation = scope.$eval(attrs.clWaypoints).animation;
element.css('opacity', '1');
element.addClass("animated " + animation);
}
},
offset:'70%',
})
}
}
})
Below is my app.js file
angular
.module('myApp', [
'ngRoute',
])
.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/home.html',
controller: "mainCtrl"
})
.when('/about', {
templateUrl: 'views/about.html'
})
.otherwise({
redirectTo: '/'
});
});
Both the views contain elements which use the cl-waypoints="{animation:'fadeInUp'}" directive.
When the app is loaded on a browser, the animations work as expected, but when I navigate to another view and begin to scroll down the elements in that view are not animated. Anyhow if I refresh the page, it works just fine.
Looks like the page needs to be refreshed for waypoint.js to do its magic. Please let me know if there is a solution to this problem.
Would appreciate any help.
Thanks.
Was able to find a solution to this problem. Waypoint requires us to call a function (Waypoint.refreshAll()) every time we make changes to the DOM, in our case changing the view content.
Added an $viewContentLoaded event listener (in the parent controller) to call the method whenever the view changes.
$scope.$on('$viewContentLoaded', function(event){
$timeout(function() {
Waypoint.refreshAll()
},0);
});
Hope this helps others.

How to ng-show element on scroll when it is into view in Angular JS

That's my code and it doesn't work. What's wrong with it?
HTML
<i ng-show="whenIconsIntoView()" class="fa fa-mail"></i>
JS
app.controller('AboutCtrl', ['$scope', '$window', function($scope, $window){
// some function to detect is scroll into user's view
isScrolledIntoView = function(elem) {...}
$window.onscroll = function() {
$scope.whenIconsIntoView = function(){
$scope.$apply(function() {
return isScrolledIntoView('.icons');
});
};
};
}]);
Give a try by more angular way code
app.controller('AboutCtrl', ['$scope', function($scope, $window) {
// some function to detect is scroll into user's view
isScrolledIntoView = function(elem) {...
}
$window[0].on('scroll', function() {
$scope.whenIconsIntoView = function() {
$scope.$apply(function() { //need to run digest cycle from events
return (isScrolledIntoView('.icons')) ? true : false;
});
};
});
}]);
And I'd like to suggest that you should not play with DOM from angular controller, I think the directive would be best way to this what isScrolledIntoView = function(elem) {...} function is doing.
You can add an event listener in your JS file to listen for the position of that ID. In other words, you'll probably need to make a directive that is attached to the thing you want to display, and in that directive you want to have the position always sending. So, use jquery in a new directive, and have it ping it's position, then if the position of that is a y value that is on the monitor bingo.
In other words, if your monitor is 1000px high, and your element is 1400px below, you scroll 400px downward, jquery will recognize that your window scroll height has changed, then you can fire off a function.
Don't want to give you the code, but a high-level understanding. Does this help?
$("#scrollid").scroll(function(){
// get current window height
//then get id position on page
// if window scroll overlaps with id position, set display:bock or whatever
})

Onsen-UI: how to update the ons-screen page attribute and reload the view?

I've just started using Onsen UI by implementing the bootstrap example and I've been trying to get the view to update when I change the page attribute.
<body ng-controller="PageLoadingCtrl">
<ons-screen page="{{loadedPage}}"></ons-screen>
</body>
My controller's code:
app.controller('PageLoadingCtrl', ['$scope', '$timeout', 'notificationService',
function($scope, $timeout, sharedService){
$scope.loadedPage = "index.html";
$scope.updatePage = function(page){
$timeout( function (){
$scope.loadedPage = page;
$scope.$apply();
}, 500);
};
$scope.$on('changePage', function (event, message){
$scope.updatePage(message);
});
}
]);
As you can see I'm using a controller on the body object so that I can update the loadedPage variable however, when I fire the changePage event, the view doesn't change.
After checking the DOM elements with web inspector I can see that page attribute is equal to whatever I pass to the updatePage function.
So far I tried to force a refresh with $apply and $digest but that still doesn't do the trick.
Cheers!
Because ons-screen need to maintain page stack. it's not intuitive to use binding for the page attribute.
Using ons.screen.presentPage()/dismissPage()/resetPage() is the preferred way.

Conditionally animating ng-view transitions

I am trying to apply animations to ng-view (routing) depending of the views involved.
For example, from View1 to View2 I need the View1 leaving through the left side and View1 entering from the right side. Otherwise, from View2 to View1 I need View2 leaving through the right side and View1 entering from the left side.
But I have also situations where I need apply different animations to both views, for example, View1 leaving fading out and View2 entering scaling up.
What I am doing is using a scope associated variable as class in the ng-view:
<div ng-view class="{{transition}}"></div>
This variable is set in each route change with something like this in each controller:
$scope.transition=Global.transition;
$rootScope.$on("$routeChangeStart",function (event, current, previous) {
// Here I get the leaving view and the entering view and the kind of transition is selected
...
$scope.transition=selectedLeavingTransition; // Set the transition for the leaving view
Global.transition=selectedEnteringTransition; // Set the transition for the entering view
});
Global is a service to set the transition variable for the entering scope from the leaving scope.
This way, when a route change is detected, the current ng-view is set with the class associated to selectedLeavingTransition, and the entering ng-view is set with the class associated to selectedEnteringTransition.
For example, if the route change was from View1 to View2 the ng-views during the animation could be:
<div ng-view class="fadeOut ng-animate ng-leave ng-leave-active"></div>
<div ng-view class="scaleUp ng-animate ng-enter ng-enter-active"></div>
The CSS in this case could be:
fadeOut.ng-leave {animation:1s fadeOut;}
scaleUp.ng-enter {animation:1s scaleUp;}
Though it works, I am wondering if there is a simpler way to do it as it seems a little mess.
An alternative solution that doesn't require much code is to define your animations on your routes:
$routeProvider.when('/view1', {
templateUrl: 'view1.html',
controller: 'View1Controller',
animations: {
enter: 'enter-left',
leave: 'leave-left'
}
});
Then use a directive to retrieve the current route's animations and add them to the element:
app.directive('viewAnimations', function ($route) {
return {
restrict: 'A',
link: function (scope, element) {
var animations = $route.current.animations;
if (!animations) return;
if (animations.enter) element.addClass(animations.enter);
if (animations.leave) element.addClass(animations.leave);
}
};
});
And put the directive on the element that contains the ngView directive:
<body ng-view view-animations></body>
Demo: http://plnkr.co/edit/Y3ExDyiPIJwvVKO4njBT?p=preview
Edit: New solution.
To set animations during run-time I would use a service just like you are doing, but a directive to apply them.
Very basic example of service:
app.factory('viewAnimationsService', function ($rootScope) {
var enterAnimation;
var getEnterAnimation = function () {
return enterAnimation;
};
var setEnterAnimation = function (animation) {
enterAnimation = animation;
};
var setLeaveAnimation = function (animation) {
$rootScope.$emit('event:newLeaveAnimation', animation);
};
return {
getEnterAnimation: getEnterAnimation,
setEnterAnimation: setEnterAnimation,
setLeaveAnimation: setLeaveAnimation
};
});
And the directive:
app.directive('viewAnimations', function (viewAnimationsService, $rootScope) {
return {
restrict: 'A',
link: function (scope, element) {
var previousEnter, previousLeave;
var enterAnimation = viewAnimationsService.getEnterAnimation();
if (enterAnimation) {
if (previousEnter) element.removeClass(previousEnter);
previousEnter = enterAnimation;
element.addClass(enterAnimation);
}
$rootScope.$on('event:newLeaveAnimation', function (event, leaveAnimation) {
if (previousLeave) element.removeClass(previousLeave);
previousLeave = leaveAnimation;
element.addClass(leaveAnimation);
});
}
};
});
Demo: http://plnkr.co/edit/DuQXaN2eYgtZ725Zqzeu?p=preview
I have been working on it and I have a neater solution, what I was doing had some problems. Now I am just using the $routeChangeStart at root scope and selecting there the leaving and enter transitions.
The only problem I have is that on the routeChangeStart event I can't modify the leaving view so I can't establish the leaving transition to the ngView element class attribute. I had to set it directly through the DOM (I know that is bad practice).
I tried to modify the leaving view through a shared service, the root scope and $apply() but none of them worked. Once the routeChangeStart event is launched the view seems static.
Here is a working example: jsfiddle.net/isidrogarcia/Fs5NZ

$viewContentLoaded isn't firing

$viewContentLoaded never fires in my Angular controller. The following:
function MyController($scope)
{
$scope.$on('$viewContentLoaded', function()
{
alert("blah");
});
}
Never hits a breakpoint, and never pops up the alert. I think this is pretty standard use, so I'm not sure what it could be, but it's definitely not firing. Everything else in the controller is behaving properly.
What would cause this?
Try this instead:
function MyController($scope)
{
$scope.$watch('$viewContentLoaded', function()
{
alert("blah");
});
}
Base on your comment above, it sounds like you have something like this:
<ng-view>
<div ng-controller="MyController"></div>
</ng-view>
I'm not sure what content in the ng-view tag will do, but the $viewContentLoaded is emitted from the ng-view scope. This means, it only goes up from the ng-view and thus your controller would never catch it.
I solved this by specifying the controller in the app.js route provider rather than using ng-controller in a div in the view. I only use this method if I need to use $viewContentLoaded, otherwise I use ng-controller.
angular.module('myapp', [...])
.config(function ($routeProvider, $locationProvider) {
$routeProvider
.when('/MyRoute', {templateUrl: 'views/MyView.html', controller: MyController})
});

Categories

Resources