Raphael Martin
Raphael Martin

Reputation: 67

Ng-repeat not updating after set variable scope value in $http.get

In my AngularJS project, I have a select HTML element, that should be bound with the states of Brazil. So, I'm using a ng-repeat to populate the states, that are loaded from my project database, using a RESTful API. Then, in my controller I created a variable on scope named estados, and set an empty array for it [1]. After I call a $http.get [2], I receive the data from the base, and set it for the $scope.estados [3], that is used in ng-repeat. [4]

Controller code:

myApp.controller('myCtrl', function($scope, $http, API_URL) {
    $scope.estados = []; //[1]
    $http({ // [2]
        method: 'GET',
        url:API_URL + '/estados',
        headers: {'Content-Type': 'application/x-www-form-urlencoded'}
    }).then(function(response) {
        if(response.status == 200){
            $scope.estados = response.data; // [3]
            console.log($scope.estados);
        }
    }); 
});

HTML code:

<select name="uf" class="form-control" ng-model="Empresa.endereco.estado_uf">
   <option ng-repeat="estado in estados" ng-value="estado.uf"> {{estado.nome}}</option> <!-- [4] -->
</select>

But, the ng-repeat doesn't update with the new value. When I call de console.log($scope.estados); in the success function of $http.get.then() I can see that the $scope is updated with correct values, but it is not passed to the View. I had already tried calling $scope.$apply(); after setting $scope.estados but I it throw the error: [$rootScope:inprog] $digest already in progress.

If I instance the variable with another value it's bound, like this:

$scope.estados = [
   {
      uf: 'SP',
      nome: 'São Paulo'
   }
];

But it's never updated with the database values. What am I doing wrong? I seems to be like something related to references, but I don't know exactly what.

Upvotes: 0

Views: 2700

Answers (4)

This has been discussed in many links.

Actually we should use only objects in the scope. Primitive values will not be reflected in the digestive cycle.

It is recommended to use the Object in Scope.

$scope.object= {};
$scope.object.estados = [];
$scope.object.estados = response.data; 

HTML:

<select name="uf" class="form-control" ngmodel="Empresa.endereco.estado_uf">
<option ng-repeat="estado in object.estados" ng-value="estado.uf"> 
{{estado.nome}}</option>
</select>

Please read the below link.

https://github.com/angular/angular.js/wiki/Understanding-Scopes

Upvotes: 1

Scott Byers
Scott Byers

Reputation: 3205

I have 2 recommendations.

First - don't assign your data to a $scope variable (do a quick Google search on "The Dot Rule" for some context). Instead assign it to a controller variable with this.estados = data in your controller code, and then access it in the DOM with the ControllerAs variable ... e.g.:

ng-controller="myCtrl as myCtrl" and then in the DOM myCtrl.estados instead of $scope.estados

Note: In the $http callback function, this won't refer to the Controller anymore. You'll want to store a reference to this in the body of your Controller code so you can reference it later. I'm prone to using var controller = this; at the top of all my Controller code so it's easily accessible in any return functions.

Second ... use ng-options for your select instead of a repeater. It's much more robust and ensures proper data binding. So in your instance:

<select ... ng-options="estado.uf as estado.name for estado in myCtrl.estados"></select>

The ... above is meant to reference anything else you need in your select element, like ng-model, class, etc.

I suspect that just converting to Dot notation instead of using $scope will remedy the situation... directives like ng-if and ng-switch (anything that pulls elements out of the DOM) can make $scope variables inaccessible somewhat unpredictably. And using ng-options is always a better practice IMO.

Upvotes: 3

quirimmo
quirimmo

Reputation: 9988

You can try two changes:

1) Explicit set the track by that usually can help ng-repeat to refresh elements (even if by default it is already track by $index):

ng-repeat="estado in estados`track by $index"

2) Instead of assigning the array to a new array, just concat the results to the current empty array:

$scope.estados.concat(response.data);

Upvotes: 1

Dorival
Dorival

Reputation: 689

I couldn't test yet, but often ngRepeat loses its capabilities to track an array, if you replace the reference. Which what is happening in your controller.

A quick test you can do is to edit your controller to keep the reference to the same array, and manipulate it instead, by looping and adding your new results to your already bound array.

myApp.controller('myCtrl', function($scope, $http, API_URL) {
    $scope.estados = []; //[1]
    $http({ // [2]
        method: 'GET',
        url:API_URL + '/estados',
        headers: {'Content-Type': 'application/x-www-form-urlencoded'}
    }).then(function(response) {
        if(response.status == 200){
            $scope.estados.length = 0; // truncate array
            for(var state in response.data)
               $scope.estados.push(state); // [3]

            console.log($scope.estados);
        }
    }); 
});

Upvotes: 0

Related Questions