I am trying to use ng-grid to visualize high-frequency real-time data, but I am having problems with a memory leak. The memory leak is not present when I use a simple html table with ng-repeat.
I am using node+express on the backend and angularjs on client side.
I use socket.io to stream real-time data from the server to a table on the client side.
I have reproduced the memory problem in a simplified example:
I send 1500 messages per sec, each message is an object like this one
{id: 1, name: “name”, time: “[current date/time string]”}
after 4 minutes the browser memory is above 400MiB and after 10 minutes is above 1GiB
I have tested on Chrome and Firefox.
Here is the simplified example, am I doing something wrong? (Additional information added at the end).
server
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io').listen(server);
io.of('/test').on('connection', function (socket) {
console.log('socket connection: /test');
for (id=1; id<16; id++) {
streamData(id);
}
function streamData(id) {
setInterval(function () {
socket.emit('updateData', {
id: id,
name: "test"+id,
time: (new Date()).toString()
});
}, 10);
}
});
service using angular-socket-io
factory('testSocket', function(socketFactory) {
return socketFactory({
ioSocket: io.connect('http://localhost/test')
});
})
controller
controller('NgGridTestCtrl', function ($scope, testSocket) {
var itemsObj = {};
$scope.items = [];
$scope.gridOptions = {
data: 'items',
columnDefs: [{field:'id', displayName:'ID', width:'15%'},
{field:'name', displayName:'Name', width:'20%'},
{field:'time', displayName:'Date and Time', width:'65%'}],
enableColumnResize: true
};
testSocket.on('updateData', function(data) {
itemsObj[data.id] = data;
var values = [];
angular.forEach(itemsObj, function(value, index) {
this.push(value);
}, values);
// the data for ng-grid
$scope.items = values;
});
});
ngGrid template
<div>
<h1>ng-grid table</h1>
<div class="gridStyle" ng-grid="gridOptions"></div>
</div>
Edited to add plain table example
using a plain table gives no memory issue, the browser memory stays at about 155MiB
controller
controller('SimpleTableCtrl', function ($scope, testSocket) {
$scope.items = {};
testSocket.on('updateData', function(data) {
$scope.items[data.id] = data;
});
}).
plain table template
<div>
<h1>Simple table with ng-repeat</h1>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in items">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.time}}</td>
</tr>
</tbody>
</table>
Additional observations
The memory problem is not just related to ng-grid; it manifests itself also using the "NgGridTestCtrl" controller with the "plain table template" with ng-repeat.
The memory problem doesn't manifest itself (with ng-grid template and NgGridTestCtrl) if the frequency of the data is lower, (500 milliseconds instead of 10 milliseconds interval in the streamData function).
The memory problem is still prestent, (with plain table template and NgGridCtrl), if the frequency of the data is lower, (500 milliseconds instead of 10 milliseconds interval in the streamData function). The memory just grows at a slower rate, as one might expect.
The higher frequency data don't result in memory problems when the "SimpleTableCtrl" with "plain table template" are used.
I have not yet been able to use ng-grid with the higher frequency data. Anybody knows if ng-grid can actually perform with high frequency data?
Hi I think at first you need to find where is your memory leak. For this you can use "Heap Allocation" at Chrome:
F12 -> Profiles -> Record Heap Allocation.
Here the topic:
Object allocation tracking
Related
So I have an array of events and each event has teams participating. these two object are related but neither is a property of the other.
What I want to do is loop through every event and display every team participating in it. I try to do this using nested ng-repeats. My problem is the inner ng-repeats only displays once the outer ng-repeat has finished executing. Meaning that what ever team participated in the last event processed will be displayed in every table.
In my controller I have an array of teams and I update that array every time I get a new event, and an array of every
here is my repeating table
<div ng-repeat="event in events" ng-init="getTeams(event.eventId)">
<div class="active title">Teams in {{event.eventName}} #{{event.eventId}}</div>
<table>
<thead>
<tr>
<th>Name</th>
<th>Sport</th>
<th>Motto</th>
<th>W-L-D</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="team in teams">
<td>{{team.teamName}}</td>
<td>{{team.teamSport}}</td>
<td>{{team.teamMotto}}</td>
<td>{{team.win}}- {{team.lose}} - {{team.draw}}</td>
<td>{{team.teamDescription}}</td>
</tr>
</tbody>
</table>
</div>
and here is my controller
(function(){
angular
.module('foo')
.controller('EventController', function ($scope, $location, EventService) {
$scope.events = [];
$scope.targetEvent = {};
$scope.teams = [];
EventService.getEvents()
.then(function(events){
$scope.events = events;
});
$scope.getTeams = function(currEventId){
EventService.getTeams(currEventId)
.then(function(eventTeams) {
$scope.teams = eventTeams;
})
}
})()
I have tried using track by but tracking it by the eventId or teamId does not seem to work. Thank you in advance for your help
Although you have creating teams variable each time. It would be global and single for all events as per your current implementation. I don't think so you should be using ng-init here, rather perform that operation inside controller itself by looping over events.
EventService.getEvents().then(function(events){
$scope.events = events;
//loop over each event.
events.forEach(function(event){
EventService.getTeams(currEventId)
.then(function(eventTeams) {
//placed specific teams on event level
event.teams = eventTeams;
})
})
});
Then you just need to change the inner ng-repeat to below and remove ng-init
<tr ng-repeat="team in event.teams">
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 have been trying for a few days now with no luck. I'm building a ASP.NET MVC 5 application. I'm building a reservations application for a restaurant. The idea is to extract a days reservations group it by location with linq to entities and then send it with signalR to the client side. On the client side I want to bind this grouped query with knockout.js and then display it, and that is where everything goes wrong. Sending the grouped reservations to the client side works fine but I can't seem to make the mapping with knockout work. Please help.
Model on Server Side
var Reservations = db.BistroReservations_Reservations
.GroupBy(reservation => reservation.BistroReservations_Location.Description)
.OrderBy(reservation => reservation.Key.ToString()).ToList();
var context = Microsoft.AspNet.SignalR.GlobalHost.ConnectionManager.GetHubContext<ReservationsHub>();
context.Clients.All.TestingGroupedReservations(Reservations);
Model on Client Side
var ReservationsViewModel = function () {
var self = this;
var connection = $.hubConnection();
var hub = connection.createHubProxy('reservationsHub')
var GroupedReservations = ko.mapping.fromJS(reservations);
//Testing -map a collection object to a observalbe and display it underneath the webpage
hub.on('TestingGroupedReservations', function (reservation) {
ko.mapping.fromJS(reservation, GroupedReservations);
});
}
ko.applyBindings(new ReservationsViewModel());
Code on the client view side
<table class="table" data-bind="visible: !loading()">
<thead class=".h1 glyphicon-bold">Reservations of Selected Day</thead>
<tbody data-bind="foreach: GroupedReservations">
<tr>
<td>Shift</td>
<td>
<table data-bind="foreach:$data">
<tbody>
<tr>
<td data-bind="text:BistroReservations_GuestID"></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
Try
this.GroupedReservations = ko.mapping.fromJS(reservations);
Where is the declaration of "reservations" variable in this line:
var GroupedReservations = ko.mapping.fromJS(reservations);
From first blush you are never setting the GroupedReservations property and you are also not returning self. If you do not return self all of your properties are considered private.
In addition anything you want to be publicly accessible needs to be a property on self.
var ReservationsViewModel = function () {
var self = this;
var connection = $.hubConnection();
var hub = connection.createHubProxy('reservationsHub')
**self.GroupedReservations = ko.mapping.fromJS(reservations);**
//Testing -map a collection object to a observalbe and display it underneath the webpage
hub.on('TestingGroupedReservations', function (reservation) {
ko.mapping.fromJS(reservation, GroupedReservations);
});
**return self;**
}
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 am working with AngularJS. I am getting strange behavior from ng-repeat. I have a controller which returns me the data to ng-repeat such as:
.....
//My other JS Functions
.....
var app = angular.module('main', ['ngTable']).
controller('DemoCtrl', function($scope, ngTableParams) {
var data = [
//data in JSON form
];
$scope.tableParams = new ngTableParams({
page: 1, // show first page
count: 5 // count per page
}, {
total:data.length,
getData: function($defer, params) {
var slicedData = data.slice((params.page() - 1) * params.count(), params.page() * params.count());
alert(slicedData.length);
console.log(slicedData);
$defer.resolve(slicedData);
}
});
})
So, now one thing to note here that I am getting expected data after slicing it that I am passing to:
$defer.resolve(slicedData);
So, no issue seems here as it passes desired data.
Now, I have ng-repeat where I show data in form of tables such as:
<table ng-table="tableParams" class="table ng-table-responsive">
<tr ng-repeat="d in $data">
<td data-title="Name">
{{d.id}}
</td>
<td data-title="length">{{$data.length}}</td>
<td data-title="Age">
{{d.ciscoID}}
</td>
</tr>
</table>
So, here I am getting accurate length that is 5. But the number of records(rows) shown are 25(5*5) that each row is shown five times. Similarly, if I set the count to 10, then each record will be shown ten times.
I am unable to understand this scenario as
<td data-title="length">{{$data.length}}</td>
gives me the correct length then it should iterate correctly too.
P.S. My getData() method in the controller is called twice don't know why?
Any help/guidance regarding this will be highly appreciated. Thanks :-)
This Issue was fixed just by replacing the 'JS' and 'CSS' files with the latest ones.