title: Persisting scroll position in AngularJS after refresh
body: <p>I've got a web app I've written with AngularJS: <a href="http://asmor.com/anr" rel="nofollow noreferrer">http://asmor.com/anr</a></p>
  
  <p>This app is for helping to build decks for a particular game.</p>
  
  <p>When you add cards to your deck, they're added to a fixed-positioned div in the top right corner. That div has a dynamically-set max-height so that it won't be occluded by the bottom of the browser's window or by another fixed div in the bottom right corner of the page.</p>
  
  <p>If you hit the max height, the list of cards in the deck scrolls. See below:</p>
  
  <p><img src="https://i.stack.imgur.com/3lyFW.png" alt="enter image description here"></p>
  
  <p>Now the problem is that when you add or remove a card from the deck while it's scrolled by clicking the red or green buttons, Angular redraws the deck list and resets the scroll back to the top.</p>
  
  <p>For convenience, here's the code I'm using for the deck list:</p>
  
  <pre><code>&lt;div id="deck" class="shown-{{ deck.notEmpty }} faction{{ deck.identity.faction }}"&gt;
      &lt;div class="deckStat cardTotal"&gt;
          &lt;strong class="valid-{{ deck.enoughCards }}"&gt;Cards:&lt;/strong&gt; {{ deck.cardCount }} / {{ deck.minCards }}
      &lt;/div&gt;
      &lt;div class="deckStat agendaPointsTotal shown-{{ deck.isCorp }}"&gt;
          &lt;strong class="valid-{{ deck.enoughAgendaPoints }}"&gt;Agenda points:&lt;/strong&gt; {{ deck.totalPoints }} / {{ deck.minPoints }}
      &lt;/div&gt;
      &lt;div class="deckStat influencePointsTotal"&gt;
          &lt;strong class="valid-{{ deck.withinInfluenceLimit }}"&gt;Influence points:&lt;/strong&gt; {{ deck.influenceTotal }} / {{ deck.influenceAvailable }}
      &lt;/div&gt;
  
      &lt;div class="deckIdentity" ng-mouseover="setPreviewLink(deck.identity)"&gt;
          &lt;strong&gt;Identity:&lt;/strong&gt;
          {{ deck.identity.name }}
      &lt;/div&gt;
  
      &lt;div id="deckScrollContainer" style="max-height: {{ getMaxDeckHeight() }}px"&gt;
          &lt;ul ng-repeat="(typeName, typeHash) in deck.cardsByType"&gt;
              &lt;li class="deckTypeHeader"&gt;
                  &lt;strong&gt;{{ typeName }}&lt;/strong&gt;
                  &lt;span class="quantity"&gt;
                      ({{ typeHash.qty }})
                  &lt;/span&gt;
              &lt;/li&gt;
              &lt;li ng-repeat="(cardName, qty) in typeHash.cards" class="card faction{{ getCardFaction(cardName) }}" ng-mouseover="setPreviewLink(cardName)"&gt;
                  &lt;button class="btn btn-mini btn-success add qty{{qty}}" ng-click="addCard(cardName)"&gt;&lt;i class="icon-plus"&gt;&lt;/i&gt;&lt;/button&gt;
                  &lt;button class="btn btn-mini btn-danger remove qty{{qty}}" ng-click="removeCard(cardName)"&gt;&lt;i class="icon-minus"&gt;&lt;/i&gt;&lt;/button&gt;
                  &lt;span class="quantity"&gt;
                      {{ qty }}x
                  &lt;/span&gt;
                  {{ cardName }}
                  &lt;span class="influence"&gt;
                      {{ getInfluenceString(cardName, qty) }}
                  &lt;/span&gt;
              &lt;/li&gt;
          &lt;/ul&gt;
      &lt;/div&gt;
  &lt;/div&gt;
  </code></pre>
  
  <p>One thing I've tried was adding a function when you add or remove cards that will grab the current scroll position, and then later on replace it.</p>
  
  <pre><code>$scope.addCard = function (c) {
      $scope.setDeckScroll();
      $scope.deck.add(c);
  };
  $scope.setDeckScroll = function () {
      $scope.deckScrollPosition = document.getElementById("deckScrollContainer").scrollTop;
  };
  </code></pre>
  
  <p>And...</p>
  
  <pre><code>$scope.getCardFaction = function (card) {
      $scope._persist();
      card = getCardByName(card);
      return card.faction;
  };
  $scope._persist = function () {
      // Any weird stuff that needs to be done on redraw/refresh
      if ($scope.deckScrollPosition) {
          document.getElementById("deckScrollContainer").scrollTop = $scope.deckScrollPosition;
          $scope.deckScrollPosition = null;
      }
  };
  </code></pre>
  
  <p>As far as I can tell, Angular is redrawing the page many times when it does redraw it. I don't know when the last re-draw is going to be, and I can't just blindly set the scrollPosition every time because then if someone scrolled to the top on their own I wouldn't be able to tell the difference between that and if it had scrolled to the top because of a redraw.</p>
  
  <p>One option I've considered is using a setTimeout to clear $scope.deckScrollPosition, so that </p>
  
  <p>I was able to get it to work by clearing the scroll position variable with a setTimeout (so that every redraw would have access to the variable)</p>
  
  <pre><code>$scope._persist = function () {
      // Any weird stuff that needs to be done on redraw/refresh
      if ($scope.deckScrollPosition) {
          document.getElementById("deckScrollContainer").scrollTop = $scope.deckScrollPosition;
          setTimeout(function () {
              $scope.deckScrollPosition = null;
          }, 100);
      }
  };
  </code></pre>
  
  <p>...but this seems really hacky. I'm hoping there might be a better way. Any ideas on how I could either...</p>
  
  <ol>
  <li><p>Make angular only redraw once per change in the data (e.g. maybe I've coded something poorly that's forcing it to redraw multiple times?)</p></li>
  <li><p>Somehow cope with the redraw without resorting to setTimeout chicanery?</p></li>
  </ol>