I Have this code in my project. I try to add data from database using $http, but ng-repeat doesn't update de table, only shows a blank row.
When I check the scope, data is already there.
I've read many answers but they does not seem to be related to my problem.
<div ng-controller="TweetsController">
<table class="table table-striped table-bordered table-hover" width="100%">
<thead>
<tr>
<th class="col-md-5"><i class="fa fa-fw fa-twitter text-muted hidden-md hidden-sm hidden-xs"></i> Texto</th>
<th class="col-md-1 text-center"> Lista</th>
<th class="col-md-1 text-center"> Cuenta</th>
<th class="col-md-1 text-center"> Red</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="tuit in filtrado">
<td>{{tuit.texto}}</td>
<td class="text-center">{{tuit.lista.nombre}}</td>
<td class="text-center">{{tuit.lista.cuenta.nombre}}</td>
<td class="text-center">{{tuit.lista.cuenta.red.tipo}}</td>
</tr>
</tbody>
</table>
<div>
<pagination total-items="ufilter.length" itemsPerPage="itemsPerPage" ng-model="currentPage"></pagination>
</div>
</div>
Controller:
.controller('TweetsController', ['$scope','$http','filterFilter', function($scope,$http,filterFilter) {
$scope.currentPage = 1;
$scope.itemsPerPage = 10;
$scope.filtrado = [];
$scope.setPage = function (pageNo) {
$scope.currentPage = pageNo;
};
// retrieve tweets
$http.get('admin/twitter').success(function(tweets) {
$scope.tweets = tweets;
});
$scope.saveTweet = function(isValid) {
if(isValid) {
var tuit = {
texto: $scope.texto,
lista_id: $scope.lista
};
$http.post('admin/twitter', tuit).success(function(t) {
$scope.tweets.push(t);
});
}
};
$scope.filtrar = function(filtro) {
if($scope.tweets != undefined) {
$scope.ufilter = filterFilter(filtro, $scope.buscar);
var inicio = ($scope.currentPage - 1) * $scope.itemsPerPage;
var fin = inicio + $scope.itemsPerPage;
$scope.filtrado = $scope.ufilter.slice(inicio, fin);
}
};
$scope.$watch('tweets', function() {
$scope.filtrar($scope.tweets);
});
$scope.$watch('currentPage', function() {
$scope.filtrar($scope.tweets);
});
$scope.$watch('buscar', function() {
$scope.filtrar($scope.tweets);
$scope.setPage(1);
});
}])
EDIT:
I Solved it!
The problem is the way how the retrieve data is wrapped
$scope.tweets.push(t[0])
You need to apply to the scope
$http.get('admin/twitter').success(function(tweets) {
$scope.tweets = tweets;
$scope.$apply()
});
This is a great blog post that explains it:
http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
The reason why your ng-repeat is not updating after the $http request is due to the $http is async and your javascript turn for the controller has finished before the response is back from your $http request. So you must notify the scope that things have changed and push it to the scope.
The problem is the way how the retrieve data is wrapped
instead of this:
$http.post('admin/twitter', tuit).success(function(t) {
$scope.tweets.push(t);
});
this:
$http.post('admin/twitter', tuit).success(function(t) {
$scope.tweets.push(t[0]);
});
There is the easy way and the correct way.
This is the easy way
//Utility Functions
function Digest($scope) {
//this may not be future proof, if it isn't then you may have to try $timeout(function(){//do something})
if (!$scope.$$phase) {
try {
//using digest over apply, because apply fires at root, and I generally don't need root.
$scope.$digest();
}
catch (e) { }
}
//console.log('$scope snapshot:', $scope);
}
In your code use this by calling
$http.post('admin/twitter', tuit).success(function(t) {
Digest($scope.tweets.push(t));
});
This is the correct way.
https://docs.angularjs.org/api/ng/service/$q
Have your HTTP call wrapped in a $q promise. Then when it is either succeed or error, then fulfill the promise. The repeater will honor the promise.
For anyone else who may run into this issue where the UI just isn't updating - even after doing an $apply or sticking within a $timeout. I've just realised I made a stupid mistake and had added :: to the embedded property and forgotten about it.
So I had my ng-repeat and inside I had a {{ ::item.name }}. For those of you who don't know what the :: does; it tells angular to set the data and not set any additional watches, which in turn then stops angular trying to update that property. This is useful for data that you know won't ever change. See the below link for more information on one time bindings:
http://blog.thoughtram.io/angularjs/2014/10/14/exploring-angular-1.3-one-time-bindings.html
So my solution was just to remove the :: so: {{ ::item.name }} became {{ item.name }}
Related
I'm new in Angular (i use 1.6)
I'm facing with data binding for the first time and I can load and refresh my data on a page simply by calling the web service every 3 seconds.
But I can't figure out how to tell to angular to call also the scope functions.
When the page load for the first time I load fake data and for every row I get a call to my scope functions job.rowColor and job.rowIcon that return CSS classes based on row status.
But when the interval expires and I load fresh data from server i see my new data load on page but without CSS classes computed from my scope function.
Why?
Am I miss something?
This is my html:
<div class="container" ng-controller="JobController as job">
<div class="d-flex flex-wrap">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Status</th>
<th scope="col">User</th>
<th scope="col">Report</th>
<th scope="col">Type</th>
<th scope="col">Files</th>
<th scope="col">Output</th>
<th scope="col">Start</th>
<th scope="col">End</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in job.data track by $index" class="table-{{job.rowColor($index)}}">
<th scope="row"><span class="oi oi-{{job.rowIcon($index)}}"></span> </th>
<th>{{row.Email}}</th>
<td>{{row.Type}}</td>
<td>{{row.Mode}}</td>
<td>{{row.TotalItems}}</td>
<td>{{row.Output}}</td>
<td>{{row.StartTime}}</td>
<td>{{row.EndTime}}</td>
</tr>
</tbody>
</table>
</div>
</div>
this is my controller:
.controller('JobController',function($scope,$http,$interval){
var queue = this;
queue.loadJobs=function(){
$http.get('http://localhost:55006/jobs-list/20/null/null').
then(function(response) {
queue.data = response.data;
});
};
$interval(function(){ queue.loadJobs(); }, 3000);
queue.data=[
{Status:'running',Email:'mmassari#mail.sm',Type:'Report1',Mode:'Multipage PDF',TotalItems:23,Output:'E-Mail',StartTime:'12:05',EndTime:'--:--'},
{Status:'ok',Email:'mbaratti#mail.it',Type:'Report3',Mode:'Zipped PDF',TotalItems:39,Output:'E-Mail',StartTime:'12:05',EndTime:'10:30:15'},
{Status:'error',Email:'acanducci#mail.it',Type:'Report3',Mode:'Multipage PDF',TotalItems:120,Output:'E-Mail',StartTime:'12:05',EndTime:'10:15:55'},
{Status:'okerror',Email:'mmassari#mail.sm',Type:'Report5',Mode:'Zipped PDF',TotalItems:23,Output:'E-Mail',StartTime:'12:05',EndTime:'09:26:00'},
{Status:'wait',Email:'eemo#mail.it',Type:'Report1',Mode:'Single PDF',TotalItems:1,Output:'Download',StartTime:'--:--',EndTime:'--:--'},
];
queue.rowColor = function(index){
switch(queue.data[index].status){
case 'running': return 'primary';
case 'wait': return 'secondary';
case 'error': return 'danger';
case 'okerror': return 'warning';
case 'ok': return 'success';
}
}
queue.rowIcon = function(index){
switch(queue.data[index].status){
case 'running': return 'play-circle';
case 'wait': return 'media-pause';
case 'error': return 'x';
case 'okerror': return 'warning';
case 'ok': return 'check';
}
}
})
I have spent long time to find out what is this code issue because I thought probably it is a bug in ng-repeat. But finally I found out your issue is misspelling the status (it is Capitalized in the data).
switch(queue.data[index].status){
must change to
switch(queue.data[index].Status){
Here is the working jsfiddle:
http://jsfiddle.net/arashsoft/2k3pojpv/2/
Instead of passing in the index I suggest you always pass in the actual object.
View
class="table-{{job.rowColor(row)}}"
Controller
queue.rowColor = function(row){
switch(row.status){
.....
I am developing my first angular-app with an .Net backend.
I get my data async from a webmethod using a http.post. That all works fine.
Client-side I would like to do some simple calculations (a final row in a table which contains sums of all the data in table)
The code to do this is pretty straight forward but my problem is the data i not ready when I try to do it.
I have read that I could use a promise and a service or a factory. But I am not sure what we be the best way to go.
My code for the view:
<div ng-controller="taskCtrl as ctrl">
<div class="col-md-10 container outer">
<h1 class="center-block">{{ctrl.SprintViewModel.SprintName}}</h1>
<table id="SprintMetaDate">
<tr><td>Projekt:</td><td>{{ctrl.SprintViewModel.ProjektName}}</td></tr>
<tr><td>Periode:</td><td>{{ctrl.SprintViewModel.StartDate}} - {{Ctrl.SprintViewModel.EndDate}}</td></tr>
<tr><td>Udarbejdet af/d:</td><td>{{ctrl.SprintViewModel.MadeBy}}</td></tr>
</table>
<h3>Sprint Resume:</h3>
<br/>
{{ctrl.SprintViewModel.SprintResume}}
<h3>Sprint afslutning:</h3>
{{ctrl.SprintViewModel.SprintDemo}}
<h2>Scope og Økonomi </h2>
<h3>Sprint Opgaver</h3>
<table id="SprintTasks" class="col-md-12">
<tr><th>Opgave</th><th>Estimat</th><th>Forbrug</th><th>Udest.</th><th>*</th><th>Pris (DKK)</th></tr>
<tr ng-repeat="x in ctrl.SprintViewModel.Tasks">
<td style="width: 40%">{{ x.Description }}</td>
<td>{{ x.TimeEst }}</td>
<td>{{ x.TimeUsed }}</td>
<td>{{ x.TimeRemaining }}</td>
<td>{{ ctrl.CalcPrecisionOfEstimat(x.TimeUsed,x.TimeRemaining,x.TimeEst) | number:2}} %</td>
<td>{{x.Price}}</td>
</tr>
<tr>
<td>Ialt</td>
<td>{{ ctrl.TotalEstimat() }}</td>
<td>{{ ctrl.TotalTimeUsed() }}</td>
<td>{{ctrl.TotalTimeRemaining()}}</td>
<td>{{ctrl.TotalPrecision()}}</td>
<td>{{ctrl.TotalPrice()}}</td>
</tr>
</table>
* Forbrug + Udestående i forhold til estimat
<br/>
Udestående opgaver er planlagt ind i næstkommende sprint.
</div>
</div>
</form>
<script>
var app = angular.module('myApp', []);
app.controller('taskCtrl', function($scope, $http) {
var ctrl = this;
ctrl.SprintViewModel = null;
ctrl.TotalEstimat=function() {
var totalEstimat=0;
for (i=0; i<ctrl.SprintViewModel.Tasks.count;i++) {
totalEstimat += ctrl.SprintViewModel.Tasks[i].Estimate;
}
return totalEstimat;
}
ctrl.TotalPrecision = function () {
var totalPrecision=0;
angular.forEach(ctrl.SprintViewModel.Tasks, function (value, key) {
totalPrecision += Number(value);
});
$http.post('SprintRapport.aspx/GetSprintViewModel', {})
.then(function(response, status, headers, config) {
console.log("I success");
ctrl.SprintViewModel = response.data.d;
});
});`
As already mentioned I get a nullreference every when the page-load on all the methods in the last row, because ctrl.SprintviewModel is undefined. I have only included one of the methods for simplicity, the problem is the same for all of them.
So my question is how do I make sure that ctrl.TotalEstimat() first get called then ctrl.SprintViewModel is assigned?
You can add ng-if condition to the last <tr> which resolves to true when data is ready to populate in your controller. So you can define $scope.loading = false initially and once your code is ready to populate you set $scope.loading=true and that will call $digest cycle internally and your view gets updated.
There are several things that you could do. I've fixed this kind of issue by placing guard conditions in the functions. These check that the necessary variables have been set before continuing. So adding if (!ctrl.SprintViewModel) return; at the beginning of the function as follows:
ctrl.TotalEstimat=function() {
// Guard Condition to prevent function executing in invalid state.
if (!ctrl.SprintViewModel) return;
var totalEstimat=0;
for (i=0; i<ctrl.SprintViewModel.Tasks.count;i++) {
totalEstimat += ctrl.SprintViewModel.Tasks[i].Estimate;
}
return totalEstimat;
}
It's another option, but as you have already alluded to, I think that promises and the $q library is the proper angular way to fix this sort of thing.
This is my controller function to get data from server.
function carsController($http, $scope, $timeout) {
var vm = this;
vm.getCarData = getCarData;
function getCarData(){
$http.get('/api/getData').then(function (response) {
console.log(response.data.message);
vm.list = response.data.message;
});
}
}
Here is the data returned.
{
"message":[
{
"emp_id":1,
"emp_name":"toyota",
"city":"city1",
"nic_no":4554
},
{
"emp_id":2,
"emp_name":"sunny",
"city":"city2",
"nic_no":57412
},
{
"emp_id":3,
"emp_name":"tata",
"city":"city3",
"nic_no":1234
}
]
}
and html code to show data. I am using carsController as cars
<div class="row" data-ng-init="cars.getCarData()">
<div class="panel panel-default">
<table class="table table-bordered table-hover">
<tr>
<th>Name</th>
<th>Pages</th>
</tr>
<tr ng:repeat="vehicle in cars.list track by $index">
<td>{{vehicle.emp_name}}</td>
<td>{{vehicle.city}}</td>
</tr>
</table>
</div>
</div>
instead of showing data, UI show 100+ empty rows when page loaded.
What could be the issue?
UPDATED
If I manually set value as below, This works well.
vm.list = [
{
"emp_id":1,
"emp_name":"toyota",
"city":"city1",
"nic_no":4554
},
{
"emp_id":2,
"emp_name":"sunny",
"city":"city2",
"nic_no":57412
},
{
"emp_id":3,
"emp_name":"tata",
"city":"city3",
"nic_no":1234
}
];
the issue is, as you can see in the console log you have posted, response.data.message is an object. Not an array.
Try this instead
vm.list = response.data.message.message;
The following will bind the message array
You have two problems.
You are assigning vm.list to response.data.message, which, as NJ_93 points out, is an object. Use
vm.list = response.data.message.message;
You're not calling your getCarData() function from your controller. So the data is never fetched.
Are you sure that the problem is not because of you have used two different objects. vm.list for receiving the data from $http and using cars.list for ng-repeat
store data to $scope.vm.list
and in the UI use ng-repeat="cars in vm.list track by $index"
I'm having two tables witch renders data trough angularJs, coming from 2 c#-methods.
The tables are structured almost exactly the same. The first one below is used as I searchfield and the other one is used basiclly to render names.
My problem is that the first one works perfect, but the other one does not. And I don't see the problem. Any help would be appreciated. // Thanks!
Here are my two tables. (the first one is working)
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.18/angular.min.js"></script>
<div ng-app="searchApp">
<div ng-controller="searchController">
#*first table works*#
<span style="color: white">Search:</span> <input data-ng-click="myFunction()" ng-model="searchText">
<table style="color: white" id="searchTextResults">
<tr><th>Name</th></tr>
<tr ng-show="!!searchText.length != 0" ng-repeat="friend in friends | filter:searchText">
<td data-id="{{friend.id}}" data-ng-click="SendFriendRequest(friend.id)">{{friend.id.replace("RavenUsers/","")}}</td>
</tr>
</table>
#*Does not work*#
<input type="button" value="Get friends requests" data-ng-click="GetFriendRequests()">
<table style="color: white">
<tr><th>Friend requests</th></tr>
<tr ng-repeat="friendRequest in friendRequests">
<td data-id="{{friendRequest.UserWhoWantsToAddYou}}" data-ng-click="acceptUserRequest(friendRequest.UserWhoWantsToAddYou)">{{friendRequest.UserWhoWantsToAddYou}}</td>
</tr>
</table>
</div>
</div>
HERE IS MY SCRIPT
<script>
var App = angular.module('searchApp', []);
App.controller('searchController', function ($scope, $http) {
//Get all users to the seachFunction
$scope.myFunction = function () {
var result = $http.get("/Home/GetAllUsersExeptCurrentUser");
result.success(function (data) {
$scope.friends = data;
});
};
//Get friendRequests from other users
$scope.GetFriendRequests = function () {
var result = $http.get("/Home/GetFriendRequests");
result.success(function (data) {
$scope.friendRequests = data;
});
};
});
</script>
The first script-function called myFunction works perfect and the data coming from my c#-method looks like this:
[{"id":"RavenUsers/One"},{"id":"RavenUsers/Two"},{"id":"RavenUsers/Three"}]
The second script-function called GetFriendRequests does not work, and as far as I can see there is no difference between this data passed into here than the data passed into myFunction:
[{"userWhoWantsToAddYou":"RavenUsers/Ten"},{"userWhoWantsToAddYou":"RavenUsers/Eleven"}]
I'd suggest you use then instead of success because $http returns a promise.
If your table doesn't "render" then put a breakpoint inside success function, console.log() the data or check friendRequests inside your HTML template, e.g. using <div>{{ friendRequests | json }}</div>, to ensure you actually got data from response.
Now you do not handle exceptions at all.
Example:
result.then(function(data) {
console.log('got data')
},function(error) {
console.log('oh noes :( !');
});
Related plunker here http://plnkr.co/edit/KzY8A3
It would be helpful if you either (a) provided a plunker to your code or (b) provided the error message.
ng-repeat requires a uniquificator on each item in the repeat, which defaults to item.id. If you don't have an id field on the item, you'll need to tell angular what field to use.
https://docs.angularjs.org/api/ng/directive/ngRepeat
So I'd suggest changing
<tr ng-repeat="friendRequest in friendRequests">
to
<tr ng-repeat="friendRequest in friendRequests track by userWhoWantsToAddYou">
and see if that works.
I'm doing a directive which is gonna work similar to an excel.
As Javascript does not support matrix as native, I had to do the workaround that usually people does. Array inside array.
Everything is working so far, but I've realized that when I applies a matrix of 100x100 I do have a big problema with performance. With a small matrix it's ok, but with big ones it's pissing me off.
Another big deal for me is that while this directive does not finish its loading the rest of the APP does not load if the problematic directive does not finish.
How can I improve this my current approach to have a better performance? And
How can I make the APP to load each part of itself in an asynchronous way?
From my service I have this:
this.getDataGrid = function(column, row){
var matrix = new Array(column);
for (var i = 0; i < column; i++) {
matrix[i] = new Array(row);
}
for (var i = 0; i < matrix.length; i++){
for (var j = 0; j < matrix[i].length; j++){
matrix[i][j] = {value: '', checked: false};
}
}
return matrix;
};
From my directive:
link: function postLink(scope, element, attrs) {
scope.dataGrid = info.getDataGrid(attrs.column, attrs.row);
}
From my view:
<table border="1">
<thead>
...
</thead>
<tbody>
<tr ng-repeat="items in dataGrid track by $index">
<td><strong>{{$index+1}}</strong></td>
<td ng-repeat="item in items track by $index"><input type="text" ng-model="item.value"/></td>
</tr>
</tbody>
</table>
UPDATE:
I gave a try to quick-ng-repeat as mentioned by apairet, but didn't work so far. Looks like the quick-ng-repeat doesn't work with nested array.
<tr quick-ng-repeat="item in dataGrid" quick-repeat-list="items">
<td><strong>{{$index+1}}</strong></td>
<td quick-ng-repeat="subItem in items" quick-repeat-list="subItems"><input type="text" ng-model="subItems.value"/></td>
</tr>
UPDATE:
After some research I started to go throughout to the lazy load approach. I've found some projects with RequireJS and Sript.js and I thought this was the solution for my problem, until I realize I had a diff situation.
The lazy load approach works by a route, it loads the related files of the route in a lazy way.
However, what about when you have inside the view of your route a directive which makes everything slow? That's my case.
So, I had to do some workaround as it follows below:
DIRECTIVE
link: function postLink(scope, element, attrs) {
var maxIntervalColumnLoad = 5;
var maxIntervalRowLoad = 5;
scope.gridLoadColumn = function(index){
if (index <= maxIntervalColumnLoad) {
return true;
}else{
return false;
}
}
scope.gridLoadRow = function(index){
if (index <= maxIntervalRowLoad) {
return true;
}else{
return false;
}
}
$interval(function(){
maxIntervalColumnLoad = (maxIntervalColumnLoad + 5);
}, 1500, (attrs.column/maxIntervalColumnLoad) );
$interval(function(){
maxIntervalRowLoad = (maxIntervalRowLoad + 5);
}, 1500, (attrs.row/maxIntervalRowLoad) );
scope.dataGrid = info.generateDataGrid(attrs.column, attrs.row);
}
VIEW
<thead>
...
</thead>
<tbody>
<tr ng-if="gridLoadRow($index)" ng-repeat="items in dataGrid track by $index">
<td><strong>{{$index+1}}</strong></td>
<td ng-if="gridLoadColumn($index)" ng-repeat="item in items track by $index"><input type="text" ng-model="item.value"/></td>
</tr>
</tbody>
It solved the problem with my first load page.
But I still have performance problems when I'm typing any value in the inputs.
<input type="text" ng-model="item.value" />
I'm guessing the relation through the ng-model seems to be the problem, but I need it to keep the data sync.
Any idea how could it be achieve? Or How to improve the performance?