Reputation: 67
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
Reputation: 31
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
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
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
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