3

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();
    }
});
Murali Krishna
  • 619
  • 2
  • 9
  • 16
  • does the scrollbar work once you click the add button? Two things: you probably want to add the rebuild:me event to scope.remove since you tell the scrollbar to update when you have an add, it makes sense to also update when there's a remove. You may also want to add the event broadcast at the end of your controller so it fires on initial render without you having to click add(). Might have to go inside a $timeout(fund(){}, 0) to allow for ngRepeat to render, idk. – jCuga Jul 10 '14 at 00:10

4 Answers4

2

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');
    });
}
jCuga
  • 1,523
  • 3
  • 16
  • 28
  • This is aslo not working. I used **$scope.$broadcast('buildScrollbar');** instead of _$scope.$broadcast('rebuild:me');_ .I removed calling of buildScrollbar() function in _ngScrollbar.js_ .It is showing in the beginning perfectly, but when I click on the add button my div height is decreasing. After 4 to 5 clicks the div height is completely gone. – Murali Krishna Jul 10 '14 at 07:39
0

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

AngularJS with ng-scroll and ng-repeat

Community
  • 1
  • 1
Joao Polo
  • 2,153
  • 1
  • 17
  • 26
0

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;
}
Gromo
  • 1,609
  • 1
  • 13
  • 14
0

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

Community
  • 1
  • 1
  • 1
    This is overly complex but essentially the best solution. When you $watch($last) the function argument is not a value it's an "isLast" value, it delivers false until the last item is reached when it's true. Which means you can issue the the "rebuild:me" right there. There's no need to callback to the main controller and test the length. – Adaddinsane Jun 20 '16 at 18:51