Skip to content

@EvilClosetMonkey /angularScrollSpy.js

Embed URL


Subversion checkout URL

You can clone with
Download ZIP
An AngularJS directive implementation of Twitter Bootstrap's ScrollSpy, ported and updated from
app.directive('scrollSpy', function ($window) {
return {
restrict: 'A',
controller: function ($scope) {
$scope.spies = [];
this.addSpy = function (spyObj) {
link: function (scope, elem, attrs) {
var spyElems;
spyElems = [];
scope.$watch('spies', function (spies) {
var spy, _i, _len, _results;
_results = [];
for (_i = 0, _len = spies.length; _i < _len; _i++) {
spy = spies[_i];
if (spyElems[] == null) {
_results.push(spyElems[] = elem.find('#' +;
return _results;
$($window).scroll(function () {
var highlightSpy, pos, spy, _i, _len, _ref;
highlightSpy = null;
_ref = scope.spies;
// cycle through `spy` elements to find which to highlight
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
spy = _ref[_i];
// catch case where a `spy` does not have an associated `id` anchor
if (spyElems[].offset() === undefined) {
if ((pos = spyElems[].offset().top) - $window.scrollY <= 0) {
// the window has been scrolled past the top of a spy element
spy.pos = pos;
if (highlightSpy == null) {
highlightSpy = spy;
if (highlightSpy.pos < spy.pos) {
highlightSpy = spy;
// select the last `spy` if the scrollbar is at the bottom of the page
if ($(window).scrollTop() + $(window).height() >= $(document).height()) {
spy.pos = pos;
highlightSpy = spy;
return highlightSpy != null ? highlightSpy["in"]() : void 0;
app.directive('spy', function ($location, $anchorScroll) {
return {
restrict: "A",
require: "^scrollSpy",
link: function(scope, elem, attrs, affix) { () {
id: attrs.spy,
in: function() {
out: function() {

I am using this directive, and it works great in Chrome. However, It is not working in IE 11. The active class is not correctly assigned, and I am not able to navigate to the first anchor. Debugged it quite a bit, but not seeing anything to indicate why it is failing.


one reason it doesn't work in ie is $window.scrollY. Use pageYOffset instead.


I got this error trying to implement your code.

Error: [$compile:ctreq] Controller 'scrollSpy', required by directive 'spy', can't be found!

AngularJS docs says that The ^ prefix means that this directive searches for the controller on its parents (without the ^ prefix, the directive would look for the controller on just its own element).
In this case we have controller in scrollSpy directive, still it gives this error. Help please.


Does not work:
Error: [$compile:ctreq] Controller 'scrollSpy', required by directive 'spy', can't be found!


You are missing the scroll-spy in your markup, it should look like (notice the first line):

<div class="row" scroll-spy>
  <div class="col-md-3 sidebar">
      <li spy="overview">Overview</li>
      <li spy="main">Main Content</li>
      <li spy="summary">Summary</li>
      <li spy="links">Other Links</li>
  <div class="col-md-9 content">
    <h3 id="overview">Overview</h3>
    <!-- overview goes here -->
    <h3 id="main">Main Body</h3>
    <!-- main content goes here -->
    <h3 id="summary">Summary</h3>
    <!-- summary goes here -->
    <h3 id="links">Other Links</h3>
    <!-- other links go here -->

I have found a couple of things that may help people trying to get this code to work for them.

1) Modify the "click" event handler on the "spy" directive to call scope.$apply().

For example: () {
   scope.$apply(function () {

The call to $apply is in Alxhill's CoffeScript code, so must have accidentally gotten removed when converting to JavaScript.

2) If you're using Angular routing (ngRoute / ngView), make sure to set "reloadOnSearch" to false so that the view doesn't reload every time $location.hash is called.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.