2

I try to implement scroll to the last added entry whithin ngRepeat directive.

I found and investigated some related SO questions: #1, #2. But still can't solve the problem.

The code is pretty simple. When the user adds new record to the array, the controller broadcasts 'scroll-event' with the new added item's name, than the directive receives this event and scroll to the new added entry in the list.

<div ng-controller="MainCtrl">
    <input ng-model="newItem" ng-keydown="$event.which === 13 && addNewItem(newItem)"/>
        <button ng-click="addNewItem(newItem)">Add</button>

        <div id="items-list" scroll-to-new-item> 
            <div ng-repeat="item in items | orderBy: 'name'">
                <input ng-model="item.name" ng-readonly="true"/>
            </div>
        </div>
 </div>

app.controller("MainCtrl", function($scope){
    $scope.items = [
        {id: 1, name: "aaa"}, {id: 2, name: "bbb"},         
        {id: 3, name: "ccc"}, {id: 4, name: "ddd"},
        .......
    ];

    $scope.addNewItem = function(newItem){
        $scope.items.push({name: newItem});
        $scope.$broadcast('scroll-event', newItem);
        $scope.newItem = "";
    };
});

This is my directive, which should scroll to the last added record to the list.

app.directive("scrollToNewItem", function(){
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            scope.$on('scroll-event', function (event, newValue) {
                if(newValue){
                    var newItemHeightPosition = // !!! MY ISSUE HERE !!!
                    $('#items-list').animate({
                        scrollTop: newItemHeightPosition
                    }, 'slow');
                }
            });
        }
    };
});

But I can't figure out, how to get the new added item's top-height position. I tried several variants, but they didn't get any results. For example the following jquery selectors doesn't work:

$('#items-list').find('input[value=' + newValue +']');
$('#items-list').find('input').eq(newValue);

Please note, that the array is sorted in alphabetical order, so that complicates this task.

This is my JSFiddle.

Who knows, how to solve this issue?

Thanks in advance!

Community
  • 1
  • 1
Artyom Pranovich
  • 6,814
  • 8
  • 41
  • 60

2 Answers2

1

There were two problems in that code.

1) The first problem was by the reason, that ngRepeat directive didn't render new added item in the repeat before scrollToNewItem directive started.

I've fixed it with wrapping of DOM selector in the $timeout. It provides a safe method to call $scope.$apply and ensures that $digest cycle has passed. You can read more details here: Angular $scope.$apply vs $timeout as a safe $apply.

2) The second problem was related with that my rendered input items didn't have any value-containing attributes. So, by this reason I can't find any DOM items with the following JQuery selectors.

$('#items-list').find('input[value=' + newValue +']')

I've fixed it by adding an additional attribute value to input whithin ngRepeat directive.

value="{{vehicle.Name}}"

Finally, my edited JSFiddle with the working example you can find here.

Community
  • 1
  • 1
Artyom Pranovich
  • 6,814
  • 8
  • 41
  • 60
0

I don't make a case for efficiency, but unless your list numbers in the 1000, i personally don't mind using .each() like this.

app.directive("scrollToNewItem", function () {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            scope.$on('scroll-event', function (event, newValue) {

                var newItemHeightPosition

                if (newValue) {
                    $('#items-list input').each(function () {
                        if ($(this).html().indexOf(newValue)) {
                            newItemHeightPosition = $(this).position().top;
                        }
                    })

                    $('#items-list').animate({
                        scrollTop: newItemHeightPosition
                    }, 'slow');
                }
            });
        }
    };
});

To explain further, when in your own example you write

$('#items-list').find('input[value=' + newValue +']');

what you are doing is trying to select a dom-element that for example could look like this

<input type="submit" value="name">

But your input-items doesn't actually even have a value attribute. Instead, i itterate over the inputs and find the one whose html contains the name just added. It is not perfect, but it should illustrate how you can work around your problem.

NachoDawg
  • 1,533
  • 11
  • 21