Search code examples
javascripthtmlangularjsangularjs-ng-repeatangularjs-ng-click

A button in an ng-repeat that is changing 10 times per second causes it not to be clickable


Description

I have part of a web page that shows each teams name and score and it has two buttons for each team (Score -1, and Score +1). enter image description here

Teams is an array and the teams are shown using an ng-repeat.

    <!-- Teams Info & Control -->
    <div class="row center-text" style="height: 40%;">
      <div class="col" ng-repeat="team in game.teams">
        <h5 style="display: inline;">{{team.name}}</h5>
        <i class="fa fa-edit" ng-click="editTeamName($index)" class="btn btn-link"></i>
        <h6>{{team.score}}</h6>
        <div class="row">
          <div class="col">
            <button type="button" ng-click="scoreChange($index, -1)" class="btn btn-primary fill-height fill-width">Score -1</button>
          </div>
          <div class="col">
            <button type="button" ng-click="scoreChange($index, 1)" class="btn btn-primary fill-height fill-width">Score +1</button>
          </div>
        </div>
      </div>
    </div>

The teams array is part of a larger object called "game" this object is received 10 times per second from a socket.io server. It needs to be 10 times per second because there is a timer in the game object that needs to be displayed to the user with high accuracy.

How the game object is updated on the client side:

socket.on('gameUpdate', function(game) {
  $scope.$apply(function() {
    $scope.game = game
  });
});

Sample game object:

{
  gameRunning: false,
  shotClockTime: defaultFullShotClock, //Value changing 10 times per second other values may change too but not as frequently
  oldShotClockValue: defaultFullShotClock,
  teams: [{
    name: "White",
    score: 0
  }, {
    name: "Blue",
    score: 0
  }],
  inOvertime: false,
  currentPeriod: 0,
  periods: {
    mainGame: generateMainGame(defaultPeriodTime, defaultBreakTime, defaultHalfTime),
    overtime: generateOverTime(defaultOverTimePeriodTime, defaultBreakTime)
  }
}

What I believe to be the problem

When the game object changes, the teams display HTML is updated ($$!) even though no changes have been made to the teams array itself. The problem is that a mouse-down then mouse-up event usually takes longer than 0.1 seconds meaning that they will not happen to the same button so they are not registered as a click meaning that the ng-click is not called.

$$! (I think this is happening because in inspect element that section is flashing purple. Also, the button color, when hovered over, is rapidly alternating between the hover and default colors. Video)

Possible solutions

  1. Make angular-js realize nothing has changed and consequently it won't change the HTML meaning everything will be fine (IDK how to do this but if you do then this could be a solution)
  2. Have the buttons stay constant as they never change depending on what the teams array is (unless it gets bigger meaning there are more of them)
  3. Don't use ng-repeat copy and paste the code because there should always be two teams (This is a last resort solution because I may need to add more teams in the future and its "bad" code)

Solution

  • Use the track by clause in your ng-repeat like:

    <div class="col" ng-repeat="team in game.teams track by $index">
    

    Track by is a useful clause that does a few things, but one of them is that it lets angular track which items are in a list so that it knows which ones need to be rerendered or which already exist. However, if these items are from database and have a unique identifier, it's actually better to use that property as the tracker rather than the index as it performs better and allows for reordering:

    <div class="col" ng-repeat="team in game.teams track by team.id">
    

    docs: https://docs.angularjs.org/api/ng/directive/ngRepeat