Murali Krishna
Murali Krishna

Reputation: 669

ng-scrollbar is not working with ng-repeat

In my app I want to use a custom scrollbar for a div. So I used ng-scrollbar, it is working fine with static data. But whenever I get the data using ng-repeat it is not working. Please help me in this regard. Thanks in advance.

myFile.html

<style>
  .scrollme {
    max-height: 300px;
   }
</style>

<div ng-app="myapp">
  <div class="container" ng-controller="myctrl">

    <button class="btn btn-info" ng-click="add();">add</button>
    <button class="btn btn-warning" ng-click="remove();">remove</button>

    <div class="well" >
      <div class="scrollme" ng-scrollbar bottom rebuild-on="rebuild:me">
        <h1>Scroll me down!</h1>
        <p ng-repeat="mi in me">{{mi.name}}</p>
      </div>
    </div>
  </div>
</div>

myCtrl.js

var myapp = angular.module('myapp', ["ngScrollbar"]);
myapp.controller('myctrl', function ($scope) {
    $scope.me = [];
    for(var i=1;i<=20;i++){
        $scope.me.push({"name":i});
    }
    var a = $scope.me.length;
    $scope.add = function(){        
    $scope.me.push({"name":$scope.me.length+1});
    $scope.$broadcast('rebuild:me');
    }
    $scope.remove = function(){
        $scope.me.pop();
    }
});

Upvotes: 3

Views: 9203

Answers (4)

user1741851
user1741851

Reputation:

This might be a bit late.

The problem is even though you have added the content to scope variable, angular has not finished adding p tags to your DOM. If you try a simple console log like

console.log($('.well').find('p').length);

After pushing content to $scope.me, you will understand what is happening. (Need jQuery at least to debug)

The solution is far more complicated than you can imagine.

STEP 1:

Add a ng-controller to your ng-repeat (Yes. It is allowed)

<p ng-repeat="mi in me" ng-controller="loopController">{{mi.name}}</p>

STEP 2: Define loopController

demoApp.controller('loopController', function($scope) {
    $scope.$watch('$last', function(new_val) {
        new_val && $scope.$emit('loopLoaded', $scope.$index);
    });
});

This controller function is triggered whenever ng-repeat manipulates DOM. I'm watching $last which is a scope variable for ng-repeat. This will be set to true whenever, ng-repeat loads last element in DOM. When $last is set to true I emit one event loopLoaded. Since you are pushing values into $scope.me using a loop, this event will be triggered for every push.


STEP 3: Event handling

In your SimpleController (not simple anymore, eh?)

$scope.$on('loopLoaded', function(evt, index) {
    if (index == $scope.me.length-1) {
        $scope.$broadcast('rebuild:me');
    }
});

Once all the p elements are loaded, index sent to event will be equal to $scope.me.length-1. So you call scroll rebuild. That's it.

Here's a reference I used - AngularJS - Manipulating the DOM after ng-repeat is finished

Upvotes: 0

Gromo
Gromo

Reputation: 1609

If you use jQuery, you can try jQuery Scrollbar - it has more options and fully CSS customizable.

Example with ng-repeat is here

JavaScript

var demoApp = angular.module('demoApp', ['jQueryScrollbar']);

demoApp.controller('SimpleController', function($scope){

    $scope.me = [];
    for(var i=1;i<=20;i++){
        $scope.me.push({"name":i});
    }

    $scope.add = function(){
        $scope.me.push({"name":$scope.me.length+1});
    }
    $scope.remove = function(){
        $scope.me.pop();
    }

    $scope.jqueryScrollbarOptions = {
        "onUpdate":function(container){
            setTimeout(function(){
                // scroll to bottom. timeout required as scrollbar restores
                // init scroll positions after calculations
                container.scrollTop(container.prop("scrollHeight"));
            }, 10);


        }
    };
});

HTML

<div data-ng-app="demoApp">
    <div data-ng-controller="SimpleController">
        <button class="btn btn-info" ng-click="add();">add</button>
        <button class="btn btn-warning" ng-click="remove();">remove</button>
        <div class="scrollbar-dynamic" data-jquery-scrollbar="jqueryScrollbarOptions">
            <h1>Scroll me down!</h1>
            <p ng-repeat="mi in me">{{mi.name}}</p>
        </div>
    </div>
</div>

CSS

.scrollbar-dynamic {
    border: 1px solid #FCC;
    max-height: 300px;
    overflow: auto;
}

Upvotes: 0

Joao Polo
Joao Polo

Reputation: 2163

please try ng-scroll... another plugin, but without need of manual adjust. mentioned on:

AngularJS with ng-scroll and ng-repeat

Upvotes: 0

jCuga
jCuga

Reputation: 1543

Try adding the broadcast call to the end of your controller so it fires on controller load. If that doesn't work, try adding:

$timeout(function () {
    $scope.$broadcast('rebuild:me');
}, 0);
// 0 optional, without it the time is assumed 0 which means next digest loop.

at the end of your controller code, not inside the add function. If this works but the previous approach doesn't then that means ngRepeat didn't finish rendering it's dynamic content in time for the ngScrollbar to properly update.

UPDATE: in general, you might have to wrap the broadcast inside of the add() function in a timeout as well. The reason I say this is that I suspect what's going on is that you add data to the scope variable and then broadcast all in the same function call. What might be happening is that the broadcast event is caught and scrollbar recalculates before ngRepeat sees the updated scope data and adds its extra DOM elements. Btw, if you want to recalculate the scrollbar on add(), then you also want to do this on remove() as well.

So your add function would become:

$scope.add = function(){        
    $scope.me.push({"name":$scope.me.length+1});
    // wait until next digest loop to send event, this way ngRepeat has enough time to update(?)
    $timeout(function () {
        $scope.$broadcast('rebuild:me');
    });
}

Upvotes: 2

Related Questions