I have a standard enough dynamically filled table in bootstrap that is instantiated with the following definition
<table>
<tr ng-repeat="c in controller.items :
orderBy:controller.predicate:controller.reverse">...</tr>
</table>
And a pretty standard function that, when called with a string argument, will sort the table, using the string argument as the predicate.
controller.orderCasesBy = function (predicate) {
controller.reverse = (controller.predicate === predicate) ?
!controller.reverse : false;
controller.predicate = predicate;
}
The page I'm working on potentially has upwards to a thousand rows, so I was trying to figure out a way to track the progress of the ordering function and update a progress bar accordingly.
If this were a case where I was trying to track the progress of the entire table rendering for the first time, I could attach an ng-init function to each repeated row that could update the progress bar whenever it's called. ng-init functions don't seem to be called when a table is reordered, however, so I was wondering if there was anything I could do to achieve a similar effect.
If I can't, I'd love to hear suggestions on alternative ways to track this.
You can add an object parameter to the ordering function, which updates its value property (or call it whatever you want), then use the object.value to render your status indicator as you see fit.
Since you said you're open to alternatives, you don't need to actively reverse it at all (and therefore don't need to indicate progress for it). ngRepeat allows you to track by index, which means you can simply display them in reverse index order.
Example:
<table>
<tr ng-repeat="c in controller.items track by $index">
<td ng-model="controller.items[controller.items.length-($index+1)]"></td>
</tr>
</table>
Related
Using angularjs (1.3) with webapi here.
I have UI where the user can upload a excel file. My api reads the excel file and returns rows data back to the UI in JSON.
The UI then reads the JSON and binds it back the UI table.
The rows and columns of this UI table are dynamically generated and are not fixed, because of which I am using contenteditable in HTML as the user can add more rows.
I can read from the the JSON fine and populate the array that holds these json values. The issue is while rendering, the screen is frozen and takes time to render all the data.
I am currently binding about 800 rows and the screen freezes and takes about 10-15 seconds or more to fill up the UI table. I would be having lot more data so looking for a solution for this.
I tried to debug and can see that there is no issue getting data back from the API, and reading JSON from the API. There is also no issue while populating the array.
Once the array populates thats when the issue comes. The UI freezes and takes time to render this data.
I am not sure whats going on here or why it takes so time to render. Below is some sample relevant code:
//Read json from the API
$http.get('https://api.myjson.com/bins/d1ugw').success(function(data) {
if (data.length > 0) {
$scope.setJson = data;
$scope.initializeTable(true);
var columns = $scope.targetTable.columns;
//These are the 3 columns of the Table but can vary dynamically(currently just hardcoding it)
var refColName = "state, month , year";
//Push the columns to the table array
var colArray = refColName.split(',');
for (var i = 0; i < colArray.length; i++) {
$scope.targetTable.columns.push({
id: columns.length,
refColName: refColName.split(',')[i]
});
}
//Read the values from the json
var idValues = $scope.getTableValues($scope.setJson, 'id');
var commentValues = $scope.getTableValues($scope.setJson, 'comment');
var rowValues = $scope.getTableValues($scope.setJson, 'refcol');
var setIdValues = $scope.getTableValues($scope.setJson, 'sid');
//Push the data back to the table array.
$scope.pushRowData($scope.targetTable, rowValues, commentValues, idValues, setIdValues);
//Till the above steps everything happens quickly and I can see $scope.targetTable being populated with my json.
//But after the above step the screen just freezes and takes time to show the entire data on the UI table.
}
});
Below is the relevant code for the UI:
<tbody>
<tr ng-repeat="r in targetTable.rows">
<td class="fixed-width">
<span>
<a class="btn-xs" ng-show="row == $index" ng-if="targetTable.rows.length > 1"><i class="fa fa-times-circle" aria-hidden="true"></i></a>
</span>
<span contenteditable="true" ng-model="r.tableId" ng-change="addNewRow(r.tableId, r)">{{r.tableId}}</span>
</td>
<td class="fixed-width" contenteditable="true" ng-repeat="column in targetTable.columns" ng-model="r[column.id]" ng-change="rowDataChange(r[column.id])"></td>
<td class="comment-fixed-width" contenteditable="true" ng-model="r.comment" ng-change="rowDataChange(r.comment)"></td>
<td class="blank fixed-width" colspan="2" ng-model="r[column.id]"></td>
</tr>
</tbody>
I have created the below JSFiddle to show my example and issue I am facing.
http://jsfiddle.net/aman1981/u1vbeos5/312/
I have also added comments in my jsfiddle for showing what method does what.
Would appreciate if anyone can help my resovling this issue.
Here are some performance stats:
with contenteditable (~4000 digest calls) = 1.800ms -> http://prntscr.com/lweugn
without contenteditable (~4 digest calls) = 1.300ms -> http://prntscr.com/lweusn
with pagination just showing the first 50 results = 0.200ms -> http://prntscr.com/lwev09
You loose the most performance because of the DOM changes obviously. But keep in mind that the number and duration of digest cycles is key for good performance. Especially when you have a huge amount of watchers. Here is a Direct comparison:
http://prntscr.com/lwf1nn As you can see the digest loop is burning 30% of your performance overall but is not the reason for your frame drop. The frame drop is mostly caused of the DOM changes. Drawing such a big table takes some time.
Further the table starts rendering when your REST call is finished. This call takes in my case roughly additional 1.700ms. So it takes nearly 3.500ms from start until rendered results. Even with pagination 1.900ms.
I would recommend a pagination with search but you can try to increase the performance anyway.
Helpful links would be:
https://stackoverflow.com/a/47347260/8196542
https://www.codeproject.com/Articles/1173869/%2FArticles%2F1173869%2Fng-repeat-performance-degradation-at-case-of-very
First, I recommend that you upgrade the Angular version to the most advanced of course if possible.
Also check the animate version.
Beyond that you thought of using an advanced tabular component such as ag-grid ?,
I can load 10000 rows without any problem with it.
https://www.ag-grid.com/
Your code is triggering the $digest loop over and over.
The "watch" method counts how often the $digest cycle is actually called:
var nbDigest = 0;
$scope.$watch(function() {
nbDigest++;
console.log(nbDigest);
});
I bet this is the cause of your performance issues.
How can I add a custom search condition to a smart table ?
Usually, we set the search for a column like this :
<input st-search="description" placeholder="Description..." type="search"/>
But is there a way to use a custom function for the search ?
Using st-set-filter will change the behavior of st-search and this is not what I want.
For now, I'm adding the condition when building the table lines like this :
<tr ng-repeat="request in r.displayedRequests"
ng-if="r.isInArray(r.developersList, request.developers && (request.status != 'CAN')">
<td>{{request.id}}</td>
<td>{{request.description}}</td>
<!-- ... -->
</tr>
Doing this filters the proper lines but this makes the pagination fail, since the displayedRequests is not updated like when using st-search.
So, how can I add a condition using my controller variables to filter my table lines ?
(And still being able to filter by global or column search using st-search on different text inputs)
Doing this filters the proper lines but this makes the pagination fail
you should really not filter your table rows using ng-if when using ng-repeat. ng-repeat expressions can take their own filter arguments and can accept your own custom filters.
ng-repeat="item in vm.items | filter: thoseIDislike"
this will also kill your pagination, so use the st-safe-src directive to save a safe copy of your data and repeat by it
sample plunker
But is there a way to use a custom function for the search ?
yes, you can use ng-change on the input, when you will change the input's model value, the function will run (to prevent it from running on each key stroke, i'm using ng-model-option's debounce property)
<input st-search="firstName"
placeholder="first name"
ng-model="fname"
ng-model-options="{ debounce: 1000 }"
ng-change="alertMe(fname)">
in the controller:
$scope.alertMe = function(message) {
if (message.length>0) {
alert(message);
}
}
I'm trying to do a search with product results using AngularJS. I've obtained an JSON object with my results": example below
[{"store_id":"17","user_id":"29","company_name":"Liquor R Us","company_type":"Alcohol",
"phone":"(303) 555-5555","website":"http:\/\/liqourrus.com","address":"5501 Peoria St",
"address_2":"","city":"Denver","state":"CO","zip":"80239","lat":"39.796181",
"lng":"-104.84863","mon_open":"","mon_close":"","tues_open":"","tues_close":"",
"wed_open":"","wed_close":"","thurs_open":"","thurs_close":"","fri_open":"","fri_close":"",
"sat_open":"","sat_close":"","sun_open":"","sun_close":"","distance":"1.1668156112981596",
"s":"s","st":"Every Store"}]
I'm using ng-repeat="store in storeResult" and the results will not display until I click on my Filter function, please note the filter is not applied!
<div class="product" ng-repeat="store in storeResult">
{{store.company_name}}
</div>
ng-click="setFilter('all');"
$scope.setFilter = function(filter) {
if(filter == 'all') {
$scope.searchProduct.product_type = '';
$scope.searchStore.company_type = '';
}
}
If I click the "setFilter" button, all results show. I'm trying to figure out how to make it display without having to click the button.
This work around only works in Chrome. Firefox and IE, never display results.
Plunker: link
My best guest is that by clicking the setFilter button you are triggering a digest cycle and your ngRepeat will be executed because of that. I suspect that you are assigning your storeResult outside the digest cycle and that's the reason is not displaying initially. I cannot tell for sure because is not in your description how is that JSON assigned to your storeAssignment .
Can you check how is that variable assigned?
Can you change the way it's and do this:
$timeout(function() {
$scope.storeAssignment = _your_json_value;
});
You will have to include the $timeout dependency but with this, a change in storeAssignment will be inside your digest cycle and the ngRepeat will see that change.
Hope that helps.
It looks like you're not setting the filter to 'all' until you click the filter function. Make sure that filter starts out as 'all'.
I have rows where cells are populated with words from an API. I've added a toggle feature on the cells to show if a user selected it. I'm using ng-class={selected:toggle} ng-click="toggle = !toggle" to toggle a CSS class to show the cell is selected or not.
HTML
<tr ng-repeat="row in game.rows">
<td id="row-{{$parent.$index+1}}-col-{{$index+1}}" ng-repeat="word in row.words" ng-class={selected:toggle} ng-click="toggle = !toggle"><div class="points">{{generateRandomPoints()}}</div>{{word}}</td>
</tr>
I added a Math function to the controller to randomly assign points to each cell (<div class="points">{{generateRandomPoints()}}</div>):
JavaScript
$scope.generateRandomPoints = function(){
return Math.floor((Math.random()*5)+2);
};
This function appears to be blocking my ability to use the toggle feature. Another oddity is that each click of a cell re-generates random numbers from the Math function.
Any thoughts why the toggling would be blocked?
It appears your generateRandomPoints call is punching through the local scope of your repeated element. Try creating a function like this:
$scope.toggle(index) {
game.rows[index].toggle = !game.rows[index].toggle;
}
And call it like ng-click="toggle($index)".
Full documentation for ngRepeat can be found here.
I've got an angular app which has a table defined in the controllers like so:
$scope.rowHeaders = ["Revenues","Costs","Profit"];
$scope.data = [[100,230,300,400,500,600,700,800,900,1000,1100,1200], [30,40,50,60,70,80,90,100,110,120,130,140], [70,160,250,340,430,540,630,720,810,900,990,1100]];
The rowHeaders apply to each row in the data object. This is connected to the template as follows:
<tbody>
<tr ng-repeat="row in rowHeaders track by $index">
<td>{{row}}</td>
<td ng-repeat="cellData in data[$index] track by $index">
<input id="data{{$parent.$index}}_{{$index}}" type="text" class="clsinputboxes" ng-model="data[$parent.$index][$index]" name="data{{$parent.$index}}_{{$index}}" ng-blur="updateColor($parent.$index, $index, data[$parent.$index][$index])" style="background-color: {{color[$parent.$index][$index]}}">
</td>
</tr>
</tbody>
This produces a simple table with input boxes. When the input in a box changes, the background color of the box will also change with code that is inside updateColor(), specified in the controller.
All of this works fine. I want to be able to write a dead-simple end-to-end test, that will change the contents of one of the boxes in the table, and then check that the updateColor() function was triggered properly (perhaps by checking that the background color has indeed changed). This is proving to be incredibly hard to do.
I had thought something as simple as the following should have worked:
input("data[1][1]").enter(20);
But this actually does not work, and I get the following error:
http://localhost:8000/test/e2e/scenarios.js:17:4
Selector [ng\:model="data[1][1]"] did not match any elements.
Whereas, the code works fine when not in test mode, and the model is bound properly in the input tag using ng-model="data[$parent.$index][$index]".
For and end-to-end test, how do I go about entering some data into any box in the table that is linked up to an array model?
I also tried the following (although I'd much rather work with input().enter()):
element("#data1_1").query(function(el, done) {
el.click();
el.val(20);
done();
});
This changes the box contents but does not trigger updateColor(). I also tried putting e1.blur() - that does not work either.
I was able to finally get this to work in the elegant end-to-end scenario runner of Angular.
First, I gave an id to the table tag - "inputTable".
Then, the following in the scenario runner did the trick:
using("table#inputTable tbody tr:eq(1) td:eq(2)").input("data[$parent.$index][$index]").enter(20);
A shoutout to Ari Lerner who helped me figure this out.