Angular controller not display data from JSON - javascript

I am using Angular and TingoDB (Mongo) inside Node Webkit for a single page application. However I have a strange problem that I have been unable to resolve.
When I use an object literal (option 2) the data displays correctly in the html page. However changing the code to return data from the database (option 1) the results do not appear on the html page. I have converted both styles of data into the a JSON string to prove consistency and then using the angular.fromJSON to return an object. Both methods return the same JSON string in console.log and before anyone asks I have either Option 1 or Option 2 commented out so both are not running concurrently.
I have copied the JSON string based on the data passed from TingoDB into the console.log and re-entered it into the code below to ensure that no differences between the 2 versions of the data existed without changing any other code, but the problem still persists.
Can anyone shed light on why this occurs and how to fix it?
var app = angular.module('myApp', []);
var Engine = require('tingodb')(),
assert = require('assert');
var db = new Engine.Db('./db', {});
var collection = db.collection("clean.db");
app.controller('tingoDataCtrl', ['$scope', function($scope) {
function getData(callback) {
//Option 1
collection.find().toArray(function(err, docs){
callback (JSON.stringify(docs));
});
//Option 2
var docs = [
{name:"tingo1", description:"56",_id:2},
{name:"tingo2", description:"33",_id:3},
{name:"tingo3", description:"22",_id:4},
{name:"tingo4", description:"76",_id:5},
{name:"tingo5", description:"99",_id:6}
];
callback (JSON.stringify(docs));
}
function info(b) {
// I'm the callback
console.log(b);
$scope.items = angular.fromJson(b)
}
getData(info);
}]);
And the Html
<body ng-app="myApp" id="main">
<div class="page page-data ng-scope">
<section class="panel panel-default" ng-controller="tingoDataCtrl">
<div class="panel-heading"><span class="glyphicon glyphicon-th"></span> Tingo Data</div>
<table class="table">
<thead>
<th class="col-md-4">
Name
</th>
<th class="col-md-8">
Description
</th>
<th class="col-md-8">
ID
</th>
<th></th>
<tr>
</tr>
</thead>
<tbody>
<!-- <tr class="reveal-animation" ng-repeat="item in items | filter:query"> -->
<tr ng-repeat="item in items | filter:query">
<td>{{item.name}}</td>
<td>{{item.description}}</td>
<td>{{item._id}}</td>
</tr>
</tbody>
</table>
</section>
</div>
<script src="js/tingo_problem.js"></script>
</body>

TingoDB is an asynchronous API which will work in the background without stop your app. This means that a syncronous code have no time to wait for an answer and in return it gives undefined.
In your case, you have done a asynchronous call, and it returns correctly the answer to the memory, but too late, the DOM have been updated with undefined already even if your javascript has the data (try console.log to see that it was there).
Angular has a way to be forced to update again the DOM with the new elements of the controller. it is called $apply. And the best way to use it to avoid unexpected behaviours is:
function info(b) {
// I'm the callback
console.log(b);
$scope.items = angular.fromJson(b);
if (!$scope.$$phase) {
$scope.$apply(); //forces update the view
}
}//$scope is NECESARY to be defined in the controler, avoid using it with "ControlerAs"

Related

angular performance: critical rendering path?

Im trying to optimice the page loading speed when rendering tables with many rows(columns min. 25x).
I am not experienced debugging/improving performance on angular apps so quite lost on what could be involved in this lack of speed.
Here is Chrome timeline report for 5 row query:
Here is Chrome timeline report for 100 row query:
The XHR load(api/list/json/Chemical...) increases in time as more rows are rendered on the table.
The server response with the data is returned fast(not the bottle neck):
Here is the template for the table:
<tbody ng-if="compressed">
<tr ng-if="dbos && (rows.length == 0)">
<td class="tableColumnsDocs"><div class="tableButtons"> </div></td>
<td class="tableColumnsDocs"><div>No results</div></td>
<td class="tableColumnsDocs" ng-repeat="attobj in columns track by $index" ng-if="$index > 0">
<p> </p>
</td>
</tr>
<tr class="tableRowsDocs" ng-repeat="dbo in rows track by $index">
<td class="tableColumnsDocs"><div ng-include="'link_as_eye_template'"></div></td>
<td class="tableColumnsDocs" ng-repeat="attobj in columns track by $index">
<div ng-init="values = dbo.get4(attobj.key); key = attobj.key; template = attobj.template || getAttributeTemplate(dbo.clazz + attobj.key);">
<div class="content" ng-include="template"></div>
<div class="contentFiller" ng-include="template"></div>
</div>
</td>
</tr>
</tbody>
And here templates the table will call:
<script type="text/ng-template" id="plain_values_template">
<p ng-repeat="v in values track by $index">{{ v }}</p>
</script>
<script type="text/ng-template" id="links_as_dns_template">
<div ng-repeat="dbo in values track by $index" ng-include="'link_as_dn_template'"></div>
</script>
<script type="text/ng-template" id="json_doc_template">
<textarea class="form-control" rows="{{values.length + 2}}" ng-trim="false" ng-readonly="true">{{ values | json }}</textarea>
</script>
<script type="text/ng-template" id="link_as_dn_template">
<p>{{ dbo.displayName() }}</p>
Relevant controller part:
$scope.getAttributeTemplate = function(str) {
//console.log("getAttributeTemplate"); console.log(str);
if ($templateCache.get(str + ".template")) {
return str + ".template";
}
var a = str.split(/(>|<)/);
//console.log(a);
if ((a.length - 1) % 4 == 0) {
return "links_as_dns_template";
}
var clsname = a[a.length - 3];
if (clsname == "*") {
return "plain_values_template";
}
var attname = a[a.length - 1];
var cls = datamodel.classes[clsname];
var att = cls.attribute[attname];
if (!att) {
return "plain_values_template";
}
if (att.type == "ref") {
return "links_as_dns_template";
}
return "plain_values_template";
};
I am new to angular and performance opt. so any tips on how to improove or bad practice highlight will be very helpful!
Long tables are angular's biggest evil, because of the hell-as-slow base directives such as ng-repeat
Some easy and obvious stuffs :
I see a lot of bindings in the row/cell templates without one-time binding (::). I dont think your row data is mutating. switching to one-time bindings will reduce the watchers count -> perf.
Some harder stuff :
Quick answer :
dont let angular handle the performance bottleneck
Long answer :
ng-repeat is supposed to compile it's transcluded content once. But using ng-include is killing this effet, causing every row to call compile on their ng-included contents. The key for good performance in big table is to be able to generates (yea, manually, which $compile, $interpolate and stuff) a unique compiled row linking function, with less as possible angular directives - ideally only one-time expression bindings, and to handle row addiction/removal manually (no ng-repeat, you own directive, your own logic)
You should AT LEAST find a way to avoid the second nested ng-repeat on' ng-repeat="attobj in columns track by $index"'. This is a dual repeated on each row, killing compilation &linking (rendering perf) and watcher count (lifecycle perf)
EDIT : as asked, a "naive" example of what can be done to handle the table rendering as manually (and fast) as possible. Note that the example does not handle generating the table header, but it's usually not the hardest thing.
function myCustomRowCompiler(columns) {
var getCellTemplate = function(attribute) {
// this is tricky as i dont know what your "getAttributeTemplate" method does, but it should be able to return
// the cell template AS HTML -> you maybe would need to load them before, as getting them from your server is async.
// but for example, the naive example to display given attribute would be
return $('<span>').text("{{::model."+ attribute +"}}"); // this is NOT interpolated yet
};
var myRowTemplate = $('<tr class="tableRowsDocs">');
// we construct, column per column, the cells of the template row
_.each(columns, function(colAttribute, cellIdx) {
var cell = $("<td>");
cell.html(getCellTemplate());
cell.appendTo(myRowTemplate);
})
return $compile(myRowTemplate); // this returns the linking function
}
and the naive usage :
function renderTableRows(dbos, columns) {
var $scope; // this would be the scope of your TABLE directive
var tableElement = $el; // this would be your table CONTENT
var rowLinker = myCustomRowCompiler(columns); // note : in real life, you would compile this ONCE, but every time you add rows.
for(var i=0; i<dbos; i++) {
var rowScope = $scope.$new(); // creating a scope for each row
rowScope.model = dbos[0]; // injecting the data model to the row scope
rowLinker(rowScope, function(rowClone) { // note : you HAVE to use the linking function second parameter, else it will not clone the element and always use the template
rowClone.appendTo(tableElement);
});
}
};
This is the approach i've been using to my own projects's table framework (well, more advanced, but this is really the global idea), allowing to use angular power to render the cell content ( 'getCellTemplate' implementation can return html with directive, which will be compiled), using filter even including directives in the cell, but keeping the table rendering logic to myself, to avoid useless ng-repeat watch, and minimizing the compilation overheat to it's minimum.

AngularJS and XML, how to render it?

I am working along DB guys, they are sending me the data thru XML, and depending the kind of element they specify is what I need to display in the view.
The code you will see is a dynamic table
<table>
<thead>
<tr>
<th ng-repeat="column in cols">
<span>{{column}}</span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="column in cols"
ng-init="isXX = column.indexOf('XX') === 0">
<span ng-if="!isXX">{{row[column]}}</span>
<button ng-if="isXX" class="btn btn-xs btn-blue"
ng-click="fillOpen()">
{{column.substring(3).replace('_', ' ')}}
</button>
</td>
</tr>
</tbody>
</table>
and here is what I have in the controller
ReportsFactory.pendingBets(reportParam).then(function(data) {
if (data.length) {
gridInfo = _.forEach(data, function(item) {return item;});
$scope.rows = gridInfo;
$scope.cols = Object.keys($scope.rows[0]);
}
}
as you can see here I have this ng-init
ng-init="isXX = column.indexOf('XX') === 0" where I am telling the app, if the property I am receiving comes with XX at the index, then display a button <button ng-if="isXX" ng-click="fillOpen()">...</button> but so far, I have some more props coming with XX at the beginning, so I need to do it more dynamic.
This is how my view looks so far
what I need to know, is how to read that XML, this is the XML printed in the Nodejs terminal
[{ BET: 57635034,
CUSTOMER: 181645,
SPORT: 'NFL',
'XX_FILL OPEN': '<element><element_type>WAGER_ACTION_BUTTON</element_type><element_call>fillOpen(57635034)</element_call><element_content/></element>',
XX_VIEW: '<element><element_type>BASIC_DROPDOWN</element_type><element_call>callThisFunction()</element_call><element_content><li>1</li><li>2</li><li>3</li><li>4</li></element_content></element>',
XX_CANCEL: '<element><element_type>BASIC_CHECKBOX</element_type><element_call/><element_content>1</element_content></element>'
}]
so, the first says
'XX_FILL OPEN': '<element><element_type>WAGER_ACTION_BUTTON</element_type><element_call>fillOpen(57635034)</element_call><element_content/></element>'
WAGER_ACTION_BUTTON should be a button
the second one says
BASIC_DROPDOWN that should be a dropdown and so on, so, how should I do in order to display the proper HTML element depending on what the XML says ?
Any suggestions ?
if I understood you correctly you want to dynamically render the xml or html content to your view... I assume that element and element type are directive you have or something.
use
ngBindHtml
e.g:
<div class="col-xs-offset-1 m-r-offset-8 p-t-offset-2 font-l-16">
<span mathjax-bind ng-bind-html="question.question.body"></span>
</div>
or you might need to use the trustAsHtml function
<div class="col-xs-offset-1 m-r-offset-8 p-t-offset-2 font-l-16">
<span mathjax-bind ng-bind-html="trustAsHtml(question.question.body)"></span>
</div>
$scope.trustAsHtml = function (val) {
return $sce.trustAsHtml(val);
};
this will take your string xml (html) code and render it...
you could always build a personalize directive and use $compile as well like:
app.directive('ngHtmlCompile',function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.ngHtmlCompile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
});
and in the code just call the ng-html-compile... no need for $sce

Retrieving the id from Model to Controller in Angular JS

i have a table which is having the data retrieved from an api call from my memberController which is displayed inside ng-repeat and its working fine.
I need each Business Name of the member list to link to a separate page(edit_form.html) and display the id value, so that i can pass this along with the api call to get only this particular member detail. So i have added ng-init in my edit form page which calls the function test_funct when the page loads and retrieve each persons id there. unfortunately i am unable to retrieve the id value inside the function.
HTML Template
<div class="page" data-ng-controller="memberController">
<table>
<thead >
<tr>
<th>Business Name</th>
<th>Contact Name</th>
<th>Trade Balance</th>
<th>Cash Balance</th>
<th>Telephone</th>
<th>Account Number </th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="member in details | filter:search">
<td>{{member.businessname}}</td>
<td>{{member.person}}</td>
<td>{{member.balance_trade}}</td>
<td>{{member.balance_cash}}</td>
<td>{{member.telephone}}</td>
<td>{{member.accountnumber}}</td>
</tr>
</tbody>
</table>
</div>
I have the following controller
function memberController($scope, $http, $cookieStore) {
var token = $cookieStore.get('token');
var conId = $cookieStore.get('Cont_Id');
var exId = $cookieStore.get('ex_Id');
var member_list = "http://www.vb.com/functions/member_list.html?exchangeid=" + exId +
"&contactid=" + conId + "&token=" + token;
$http.get(member_list)
.success(function(response) {
$scope.details = response;
});
$scope.test_funct = function(id) {
$scope.myid = id;
alert($scope.myid); // getting undefined in alert, i expect the id(eg:1123)
}
}
edit_form.html
<div class="page" data-ng-controller="memberController">
<div class="panel-body" ng-init="test_funct()"></div>
</div>
Please assist me on this. Thanks in advance.
There are 2 things going on here.
First, you should separate controllers for the different views, so you end up with something like this:
<div class="page" data-ng-controller="memberController">
<table>
<!-- your whole big table here -->
</table>
</div>
And your editing form as follows:
<div class="page" data-ng-controller="editController">
<div class="panel-body"></div>
</div>
Notice that you now have two distinct controllers - your "editController" and your "memberController".
The second question then becomes, how do you transfer the selected ID from the list view ("memberController") to the edit view ("editController").
There are 2 ways of doing that.
First, you could use a service shared between the controller:
.factory('SelectedId',function() {
return {};
});
And then in your "member" view, you would set it upon clicking:
{{member.businessname}}
Notice the ng-click, which then needs a function in the memberController and the injected service:
.controller('memberController',function($scope,SelectedId) {
$scope.setId = function(id) {
SelectedId.id = id;
};
});
While the editController retrieves it:
.controller('editController',function($scope,SelectedId) {
$scope.id = SelectedId.id;
});
The above option works well, especially for complex things like shopping carts. If all you are doing is passing an ID, I would just stick it in the URL:
{{member.businessname}}
So that the ID is part of the URL. You then can retrieve it in the "editController":
.controller('editController',function($scope,$routeParams) {
$scope.id = $routeParams.member;
});
assuming you are using ng-route, and your route would look like:
$routeProvider.when('/pages/edit_form/:member',{templateUrl:'/route/to/template.html',controller:'editController'});
In html do that
<td>{{member.businessname}}</td>
...
In app.js or where you define route do that
.when('/edit/:memberid',
{
templateUrl:'partials/edit.html',
controller:'editController'
})
In controller you have to take this id by doing that
app.controller("editController",function($routeParams,$scope){
$scope.memberid= $routeParams.memberid;
//Now use this member id to fetch all data
});

Reloading using Angular

Hello friends from SO!
I'm new into angular, and I'm trying to keep a table always updated with the information comming from a PHP webservice.
I'm demanding the information the first time using the following:
HTML
<div class="block" ng-controller="demandar_informacion" ng-init="visualizacion_masiva()">
<h1 class="block_header">Welcome admin</h1>
<p class="block_info"></p>
<table>
<thead>
<tr>
<th ng-repeat="header in headers ">{{header}}</th>
</tr>
</thead>
<tr ng-repeat="disponible in disponibles">
<td ng-repeat="(variable, valor) in disponible">{{valor}}</td>
</tr>
</table>
</div>
Then I'm using the following code to get the information:
Js Angular:
function demandar_informacion($scope, $http) {
//pedido de visualizaciĆ³n masiva
$scope.visualizacion_masiva = function() {
var address = "http://127.0.0.1/usa/_code/index_records.php"
+ "?ac=view_all"
var pedido = $http({
method: 'GET',
url: address
})
.success(function(data, status) {
$scope.errors = data.error;
$scope.headers = data.headers;
$scope.disponibles = data.disponibles;
$scope.eliminados = data.eliminados;
$scope.info = data.info;
});
};
}
Main Q:
Is there any way I could re-send the HTTP packet and update the information every, let's say, 3 or 5 seconds? as It's rapidly changing.
Auxiliary:
At the same time, this fragment of code, seems to be altering the order of the values I have on the array, or it might be previously altered somewhere in the Angular code. I've checked the PHP and the Json string seems to be in right conditions, but when it comes to printing the values, it completely looses it's native order (shows the elements in an improper / unknown order)... anyone has a clue?
<tr ng-repeat="disponible in disponibles">
<td ng-repeat="(variable, valor) in disponible">{{valor}}</td>
</tr>
Thanks in advance!
Chris C. Russo
Update
$scope.update = function() {
$timeout(function() {
$http.get('lol').success(function() {
$scope.update();
});
}, 5000);
};
Old
Simplest way to do this:
$interval(function() {
demandar_informacion();
}, 5000);
buuuuutttt as MichaL pointed out in the comments what will happen is that 5 seconds will get eaten up as it becomes 5, 4, 3, 2, 1, DDOSing yourself due to the time it takes to complete the request.
Other ways:
Use firebase to wait and call the load function.
Long poll your php script.
You can use the $timeout service to call the server every few seconds. Or use websockets to push the changes from the server to your Angular app (with Ratchet, perhaps (http://socketo.me/))

ng-class directive call executed twice

Simple html:
<table class="table table-condensed">
<tr data-ng-repeat="customer in customers" data-ng-class="customerSelectedClass(customer)">
<td>
{{customer.Name}}
</td>
</tr>
</table>
In my controller - two functions to select customer and return proper class to highlight a table row:
$scope.customerSelectedClass = function (customer) {
if (customer == $scope.selectedCustomer) {
console.log('returing info for ' + customer.Name);
return "info";
}
return "";
};
$scope.selectCustomer = function (customer) {
console.log('selecting ' + customer.Name);
$scope.selectedCustomer = customer;
}
I noticed that when I click on a customer link, customerSelectedClass function executes twice. selectCustomer function on ng-click directive executes once, as it should. Angular is only included once on the page. I wonder if this is a bug in Angular or something that I am doing wrong?
Behind the scenes, angular is setting up a $watch on the function that is resolving the class name. Because angular uses dirty checking to see if there has been a change, this method will be called twice during the $digest cycle. This is ok.
I would suggest that you don't add this code the the controller though, because if you are managing many css classes, you could be adding a lot of unnecessary code. Try something like this instead:
<table class="table table-condensed">
<tr data-ng-repeat="customer in customers" data-ng-class="{'info': customer == selectedCustomer}">
<td>
{{customer.Name}}
</td>
</tr>
</table>
Then, there is no need for a controller function customerSelectedClass. This will only add the info class if the right-hand side of the : resolves to true. And there is no problem resolving the correct customer in the ng-repeat.
Hope this helps.

Categories

Resources