1

The scenario is a Sudoku game with JavaScript and AngularJS.

The Sudoku field is an Array (rows) of Arrays (columns). All arrays have a length of 9. I name this Sudoku field matrix. The function exampleMatrix() returns such a matrix which already has some fields filled.

An updateValue(sudokuMatrix, row, col, val) function changes the field at position row row and column col in the matrix sudokuMatrix to the value val.

function TestController($scope) {
    $scope.matrix = exampleMatrix();
    $scope.updateValue = updateValue;
    $scope.getMatrixVal = function getMatrixVal(row, col) {
        return $scope.matrix[row][col];
    };
}

What I want to do is to display every field of the matrix as an <input type="text"> field in HTML. I want all the fields to be initially set to the values they have in the matrix. If the input of a text field is changed, I want the value to be updated in the $scope.matrix object.

I can fill the fields with the values of the matrix with no problem with the following HTML/AngularJS Code:

<div ng-repeat="(rowIndex,row) in matrix track by $index">
    <input type="text" size="1" value="{{col}}"
           ng-repeat="(colIndex,col) in row track by $index">
</div>

But I cannot update the values in the $scope.matrix object yet, of course.

The problems I am facing are

  1. If I change the value in an input field, the matrix should update that value. I can do this with ng-change="updateValue(matrix, rowIndex, colIndex, inputVal)".
  2. In order to be able to change the matrix in the scope with ng-change, I need an ng-model, which binds the input of the field to a variable: ng-model="inputVal"
  3. If I add those two lines, the input fields are not filled initally with the values in the matrix object of the scope.

The current code looks like this:

<div ng-repeat="(rowIndex,row) in matrix track by $index">
    <input type="text" size="1" value="{{getMatrixVal(rowIndex, colIndex)}}"
           ng-model="inputVal"
           ng-change="updateValue(matrix, rowIndex, colIndex, inputVal)"
           ng-repeat="(colIndex,col) in row track by $index">
</div>

My guess is that when the site is generated, ng-model assigns the empty value of the input field to inputVal, and that ng-change is immediately triggered. Every field of the matrix would thus be populated with an empty value. AngularJS is not throwing any errors though, so I cannot be sure. Anyways, I'm at a loss here and do not know how to get the initial values to be written to the fields initially and ng-change to be only called afterwards. I appreciate every help.

JSFiddle: https://jsfiddle.net/30xkedmp/

JavaScript: http://pastebin.com/8cge8BXs HTML: http://pastebin.com/X0YUPU4h

GeckStar
  • 1,136
  • 1
  • 14
  • 22

2 Answers2

2

Why don't you just use

<div ng-repeat="(rowIndex,row) in matrix track by $index">
    <input type="text" size="1" ng-model="matrix[rowIndex][colIndex]"
           ng-repeat="(colIndex,col) in row track by $index">
</div>

instead of

<div ng-repeat="(rowIndex,row) in matrix track by $index">
    <input type="text" size="1" value="{{getMatrixVal(rowIndex, colIndex)}}"
           ng-model="inputVal"
           ng-change="updateValue(matrix, rowIndex, colIndex, inputVal)"
           ng-repeat="(colIndex,col) in row track by $index">
</div>
Patryk Łucka
  • 1,023
  • 8
  • 15
  • Didn't think of it, makes it cleaner. But I still have the problem that the values I write into the input fields won't update the matrix in the Controller. – GeckStar Nov 19 '16 at 14:37
  • ng-model is two way binding, so it updates values in controller when you change your view and updates your view when you change your controllers values – Patryk Łucka Nov 19 '16 at 17:04
  • You were indeed right, sorry. I ran into problems with my code because I parsed the input String to an int with my `updateValue(...)` method, which I can't do that easily now with ng-model. The way I checked for changes did not recognize the Strings and it seemed to me that the value did not get registered. Anyways, you're absolutely right and this code is way simpler than the other option (and your answer was the first one), so I changed my vote. – GeckStar Nov 19 '16 at 21:05
1

You can try this code and see,I have only replaced your value attribute with the ng-value attribute in input tag as shown below,

<!DOCTYPE html>
<html ng-app="app">
<head>
<meta charset="ISO-8859-1">
<title>Insert title here</title>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.9/angular.min.js"></script>
</head>
<body ng-controller="TestController">
<div ng-repeat="(rowIndex,row) in matrix track by $index">
  <input type="text" size="1" ng-value="getMatrixVal(rowIndex, colIndex)"
         ng-model="inputVal"
         ng-change="updateValue(matrix, rowIndex, colIndex, inputVal)"
         ng-repeat="(colIndex,col) in row track by $index">
</div>
<script>
angular.module('app', []);
angular.module('app').config(['$controllerProvider', function($controllerProvider) {
  $controllerProvider.allowGlobals();
}]);

function emptyRow() {
  return [0, 0, 0, 0, 0, 0, 0, 0, 0];
}

function emptyMatrix() {
  var sudokuMatrix = [];
  for (var i = 0; i < 9; i++) {
    sudokuMatrix[i] = emptyRow();
  }
  return sudokuMatrix;
}

function updateValue(sudokuMatrix, row, col, val) {
  row = parseInt(row);
  col = parseInt(col);
  val = parseInt(val);
  sudokuMatrix[row][col] = val;
}

function exampleMatrix() {
  matrix = emptyMatrix();
  updateValue(matrix, 0, 2, 6);
  updateValue(matrix, 0, 3, 3);
  updateValue(matrix, 0, 4, 2);

  updateValue(matrix, 1, 2, 1);
  updateValue(matrix, 1, 5, 5);
  updateValue(matrix, 1, 6, 3);
  updateValue(matrix, 1, 7, 6);

  updateValue(matrix, 2, 0, 9);
  updateValue(matrix, 2, 1, 4);
  updateValue(matrix, 2, 3, 6);

  updateValue(matrix, 3, 2, 4);

  updateValue(matrix, 4, 0, 8);
  updateValue(matrix, 4, 1, 7);
  updateValue(matrix, 4, 3, 1);
  updateValue(matrix, 4, 4, 3);
  updateValue(matrix, 4, 5, 4);
  updateValue(matrix, 4, 7, 5);
  updateValue(matrix, 4, 8, 6);

  updateValue(matrix, 5, 6, 1);

  updateValue(matrix, 6, 5, 9);
  updateValue(matrix, 6, 7, 4);
  updateValue(matrix, 6, 8, 7);

  updateValue(matrix, 7, 1, 1);
  updateValue(matrix, 7, 2, 7);
  updateValue(matrix, 7, 3, 5);
  updateValue(matrix, 7, 6, 2);

  updateValue(matrix, 8, 4, 7);
  updateValue(matrix, 8, 5, 2);
  updateValue(matrix, 8, 6, 5);

  return matrix;
}

function TestController($scope) {
  $scope.matrix = exampleMatrix();
  $scope.updateValue = updateValue;
  $scope.getMatrixVal = function(row, col) {
    return $scope.matrix[row][col];
  };
}

</script>
</body>
</html>

The code snippet won't function here because it doesn't seem to be accepting the version of angular js i have specified, but the rest of the code is functioning well.

You can try replacing the value attribute by the ng-value attribute in your fiddle and try

GraveyardQueen
  • 771
  • 1
  • 7
  • 17
  • I should have known that there must be a directive for it. That's exactly what I was looking for, thanks! For anyone stumbling upon the same problem, there's more information in this thread: https://stackoverflow.com/questions/23865608/what-is-the-difference-between-value-attribute-and-ng-value-attributes-in-an – GeckStar Nov 19 '16 at 14:41
  • indeed, ng-value can be used instead of ng-model in your case, but you used here too much code, what you need is just single ng-model, without need to use ng-change, getMatrixVal function or "inpuVal" variable. Just as i mentioned in comment below my answer - ng-model directive is two way binding, so it updates controllers variables automatically ; ) take a look at this question: http://stackoverflow.com/questions/28717523/whats-the-difference-between-ng-model-and-ng-value – Patryk Łucka Nov 19 '16 at 17:14