Search code examples
angularjsangularjs-scopeangularjs-ng-repeat

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.


Solution

  • 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.