Avoiding $timeout() to force digests to complete in Angular

In my Angular app the following sequence of events needs to happen:

  1. User clicks a button
  2. A previously hidden div is shown by flipping a boolean scope variable to which an ng-show is bound
  3. The newly shown view, which is further down the page, should be scrolled into view.

Currently, the scrolling does not work because this is all happening within the same digest. This means that the new state of the ng-show bound variable has not had a chance to update the DOM. Therefore, we attempt to scroll to an element that, as far as the DOM is concerned, is not yet visible.

  • $apply already in progress when sending a click event to a button?
  • ng-repeat call controller function too many times
  • “TypeError: Illegal invocation” when trying to upgrade from v1.2
  • AngularJS : How to edit $scope from the console?
  • Is the $scope.$apply() call warranted for this scenario?
  • Angularjs function inside ng-repeat called too many times
  • This is mitigated by wrapping the scroll call with a $timeout, which forces all digest(s) to complete before attempting to scroll. While this works, it feels like a hack, and I want to know if there is a better way of doing this.

    Here is a fiddle that demonstrates the problem:

    http://jsfiddle.net/fNhnj/3/

    (Note that this code is a simplified version of my real code just to demonstrate the problem, and I realize it does not follow best practices. Rest assured that my real code does not perform direct DOM manipulation in the controller.)

    View:

    <div ng-app="app" ng-controller="MyCtrl" id="container">
        <button ng-click="scroll()">Unhide element and scroll</button> (will not work until $timeout call is uncommented)
        <div style="height:1000px; background-color: #ddddd0">
        </div>
        <div id="target" ng-show="isVisible">section to scroll to</div>
    </div>
    

    JS:

    angular.module("app", [])
    
    .controller('MyCtrl', 
        function MyCtrl($scope, $timeout) {
            $scope.isVisible = false;
            $scope.scroll = function() {
                $scope.isVisible = true;
                // uncommenting the $timeout fixes it, but feels like a hack
                //$timeout(function() {
                    $('body').animate({
                        scrollTop: $("#target").offset().top
                    }, 500);
                //});
            };
        }
    );
    

  • AngularJS : How to edit $scope from the console?
  • Cannot change ng-model from inside directive
  • Angularjs function inside ng-repeat called too many times
  • Angular : Update model from directive
  • ng-repeat call controller function too many times
  • Is AngularJs digest cycle triggered after any function call?
  • One Solution collect form web for “Avoiding $timeout() to force digests to complete in Angular”

    Using $timeout is not such a big issue, because the code you are running is not an angular code, and you need to communicate to angular in some way that it needs to refresh the view.