When a user scrolls their browser window below a certain point, I am toggling the class of the #page div.
What I have done so far works fine:
<div ng-app="myApp" scroll id="page">
<header></header>
<section></section>
</div>
app = angular.module('myApp', []);
app.directive("scroll", function ($window) {
return function(scope, element, attrs) {
angular.element($window).bind("scroll", function() {
if (this.pageYOffset >= 100) {
element.addClass('min');
console.log('Scrolled below header.');
} else {
element.removeClass('min');
console.log('Header is in view.');
}
});
};
});
(when they scroll their window below the header, 100px, the class is toggled)
Although, correct me if I'm wrong, I feel that this is not the correct way to be doing this with Angular.
Instead, I presumed that the best method for doing this would be by using ng-class and storing a boolean value in the scope. Something like this:
<div ng-app="myApp" scroll id="page" ng-class="{min: boolChangeClass}">
<header></header>
<section></section>
</div>
app = angular.module('myApp', []);
app.directive("scroll", function ($window) {
return function(scope, element, attrs) {
angular.element($window).bind("scroll", function() {
if (this.pageYOffset >= 100) {
scope.boolChangeClass = true;
console.log('Scrolled below header.');
} else {
scope.boolChangeClass = false;
console.log('Header is in view.');
}
});
};
});
Although this is not dynamic, if I change the value of scope.boolChangeClass in the scroll callback, then the ng-class is not updating.
So my question is: how is best to toggle the class of #page, using AngularJS, when the user scrolls below a certain point?
This question is related to
angularjs
angularjs-directive
This is my solution, it's not that tricky and allow you to use it for several markup throught a simple ng-class directive. Like so you can choose the class and the scrollPos for each case.
Your App.js :
angular.module('myApp',[])
.controller('mainCtrl',function($window, $scope){
$scope.scrollPos = 0;
$window.onscroll = function(){
$scope.scrollPos = document.body.scrollTop || document.documentElement.scrollTop || 0;
$scope.$apply(); //or simply $scope.$digest();
};
});
Your index.html :
<html ng-app="myApp">
<head></head>
<body>
<section ng-controller="mainCtrl">
<p class="red" ng-class="{fix:scrollPos >= 100}">fix me when scroll is equals to 100</p>
<p class="blue" ng-class="{fix:scrollPos >= 150}">fix me when scroll is equals to 150</p>
</section>
</body>
</html>
working JSFiddle here
EDIT :
As
$apply()
is actually calling$rootScope.$digest()
you can directly use$scope.$digest()
instead of$scope.$apply()
for better performance depending on context.
Long story short :$apply()
will always work but force the$digest
on all scopes that may cause perfomance issue.
Thanks to Flek for answering my question in his comment:
<div ng-app="myApp" scroll id="page" ng-class="{min:boolChangeClass}">
<header></header>
<section></section>
</div>
app = angular.module('myApp', []);
app.directive("scroll", function ($window) {
return function(scope, element, attrs) {
angular.element($window).bind("scroll", function() {
if (this.pageYOffset >= 100) {
scope.boolChangeClass = true;
} else {
scope.boolChangeClass = false;
}
scope.$apply();
});
};
});
Maybe this can help :)
Controller
$scope.scrollevent = function($e){
// Your code
}
Html
<div scroll scroll-event="scrollevent">//scrollable content</div>
Or
<body scroll scroll-event="scrollevent">//scrollable content</body>
Directive
.directive("scroll", function ($window) {
return {
scope: {
scrollEvent: '&'
},
link : function(scope, element, attrs) {
$("#"+attrs.id).scroll(function($e) { scope.scrollEvent != null ? scope.scrollEvent()($e) : null })
}
}
})
What about performance?
scope.applyAsync
to reduce overall digest cycles countfunction debounce(func, wait) {
var timeout;
return function () {
var context = this, args = arguments;
var later = function () {
timeout = null;
func.apply(context, args);
};
if (!timeout) func.apply(context, args);
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
angular.module('app.layout')
.directive('classScroll', function ($window) {
return {
restrict: 'A',
link: function (scope, element) {
function toggle() {
angular.element(element)
.toggleClass('class-scroll--scrolled',
window.pageYOffset > 0);
scope.$applyAsync();
}
angular.element($window)
.on('scroll', debounce(toggle, 50));
toggle();
}
};
});
3. If you don't need to trigger watchers/digests at all then use compile
.directive('classScroll', function ($window, utils) {
return {
restrict: 'A',
compile: function (element, attributes) {
function toggle() {
angular.element(element)
.toggleClass(attributes.classScroll,
window.pageYOffset > 0);
}
angular.element($window)
.on('scroll', utils.debounce(toggle, 50));
toggle();
}
};
});
And you can use it like <header class-scroll="header--scrolled">
Directives are not "inside the angular world" as they say. So you have to use apply to get back into it when changing stuff
Source: Stackoverflow.com