ng-zorro antd, nz-table dynamic table columns and rows - javascript

I have a table where columns and row cells are dynamically set,
in the table header th content should be dynamic and also for table body tr maybe contain HTML that contains another component tag.
is there any way to handle that, I have created a component called table and this table has #Input and #Output to be reusable for different usage.
in the ng-zorro documentation, there is no way to use the table data source technique so I can use render functions like react and.

You can create two inputs, one for the columns and one for the rows.
To make the columns dynamically you have to send to the column input an array with column objects. There you can set everything that you want. I usually use the tittle and the column function like that:
listOfColumn = [
{
title: 'Code',
compare: (a: User, b: User) => a.code.localeCompare(b.code)
},
{
title: 'Customer',
compare: (a: User, b: User) => a.name.localeCompare(b.name)
}
]
The html code to use it is the following:
<thead>
<tr>
<th *ngFor="let column of listOfColumn" [nzSortFn]="column.compare">{{ column.title }}</th>
</tr>
</thead>
And for the data, just send it to other input and set the array as data input for the table and make a loop to display the content:
<nz-table
#basicTable
[nzData]="data">
<thead>
<tr>
<th *ngFor="let column of listOfColumn" [nzSortFn]="column.compare">{{ column.title }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of basicTable.data">
<td>{{ user.code }}</td>
<td>{{ user.name }}</td>
</tbody>
</nz-table>
I hope I answer your question :D

Related

An animation in Angular to switch rows in a table

I have an HTML file that displays a voting chart for musical songs using Angular. Each time I press the refresh button, the chart data is updated by making a GET request. I am trying to create an animation that shows the changes in the table when the data is updated. For example, when the third most voted track becomes the first most voted track after the update, I want the third row to visually move up in the table and the first and second rows to move down. Can someone help me figure out how to achieve this animation?
<button onclick="reloadData()">REFRESH</button>
<table>
<thead>
<tr>
<th>Rank</th>
<th>Track</th>
<th>Artist</th>
<th>Percentage</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let track of votedTracks; let i = index">
<td>{{ i + 1 }}</td>
<td class="track-name">{{ track.name }}</td>
<td class="artist-name">{{ track.artist }}</td>
<td class="percentage">{{ track.percentage | number : "1.0-2" }}%</td>
</tr>
</tbody>
</table>
I have tried to create a custom animation using the Angular animation module, but the rows are not moving as expected. I'm expecting the rows to move up or down in the table when the data is updated and the ranking of the tracks changes. Last time it showed only the first row and than it gave me the error:
> ERROR Error: Unexpected synthetic property #moveRow found. Please make sure that:
> Either `BrowserAnimationsModule` or `NoopAnimationsModule` are imported in your application.
> There is corresponding configuration for the animation named `#moveRow` defined in the `animations` field of the `#Component` decorator (see https://angular.io/api/core/Component#animations).
Import the BrowserAnimationsModule in your app.module.ts file.
import { BrowserAnimationsModule } from '#angular/platform-browser/animations';
You also need to add [#moveRow] to the <tr>, like so:
<tr *ngFor="let track of votedTracks; let i = index" [#moveRow]>
All together it would something like this:
#Component({
animations: [
trigger('moveRow', [
state('in', style({transform: 'translateY(0)'})),
transition('void => *', [
style({transform: 'translateY(-100%)'}),
animate(200)
]),
transition('* => void', [
animate(200, style({transform: 'translateY(100%)'}))
])
])
],
// ... etc
})
<tbody>
<tr *ngFor="let track of votedTracks; let i = index" [#moveRow]="i">
<td>{{ i + 1 }}</td>
<td class="track-name">{{ track.name }}</td>
<td class="artist-name">{{ track.artist }}</td>
<td class="percentage">{{ track.percentage | number : "1.0-2" }}%</td>
</tr>
</tbody>
Then in your reloadData() method you need to use ngZone.run() method because the change detection doesn't run automatically from an async data source... you would have something like this:
reloadData() {
this.http.get('/api/voted-tracks').subscribe(data => {
this.ngZone.run(() => {
this.votedTracks = data;
});
});
}

How can I populate the dropdown values for each row within a data table?

The following is my HTML.
<tr>
<th>
Test Data
</th>
<th>
Test Data Options
</th>
</tr>
<tr>
<td>
<ng-container>
{{data.testData}}
</ng-container>
</td>
<td class="text-base font-normal">
<ng-container>
<p-dropdown [options]="dropdownOptions" placeholder="Select Test Data"></p-dropdown>
</ng-container>
</td>
</tr>
This is my JSON file.
“data”: [
{
“testData”: “Test Data A”,
“testDataOptions”: [
{
“testDataOptionValue”: “Test Data A1 Option”,
“testDataOptionLabel”: “Test Data A1”
},
{
“testDataOptionValue”: “Test Data A2 Option”,
“testDataOptionLabel”: “Test Data A2”
}
],
},
{
“testData”: “Test Data B”,
“testDataOptions”: [
{
“testDataOptionValue”: “Test Data B1 Option”,
“testDataOptionLabel”: “Test Data B1”
},
{
“testDataOptionValue”: “Test Data B2 Option”,
“testDataOptionLabel”: “Test Data B2”
}
],
}
How could I populate the dropdown values in each row in the data table according to their indexes?
For example
The Test Data A row dropdown values should be:
Test Data A1, Test Data A2
The Test Data B row dropdown values should be:
Test Data B1, Test Data B2
I guess you're missing a *ngFor in the code snippet you sent:
<tr *ngFor="let data of json.data">
<td>{{ data.testData }}</td>
<td>
<p-dropdown [options]="data.testDataOptions" optionLabel="testDataOptionLabel" optionValue="testDataOptionValue"></p-dropdown>
</td>
</tr>
Edit: binding the selected value
A first option, although it might feel hacky depending on context, is to bind to the data object you already have. For instance:
<p-dropdown [options]="data.testDataOptions" optionLabel="testDataOptionLabel" optionValue="testDataOptionValue" [(ngModel)]="data.selectedValue"></p-dropdown>
If that feels weird or you just don't want to mess with that data, then you'll need to store it in a separate variable, which can be an array or a key/value structure. Then you just bind each item to the corresponding entry in the the data structure.
Allan gave you the correct answer, I'm just going to expand it a bit. Since your dropdown options list might be a large one, please consider using trackBy as well. It is not mandatory and it does not always prove much more performant, but overall it should help with performance. You can do it like so:
<tr *ngFor="let data of json.data; trackBy: trackByMethod">
<td>{{ data.testData }}</td>
<td>
<p-dropdown [options]="data.testDataOptions" optionLabel="testDataOptionLabel" optionValue="testDataOptionValue"></p-dropdown>
</td>
</tr>
While in your .ts file (make sure you add some IDs to your items as well - or use other unique identifiers):
trackByMethod = (index: number, item: any): string => item.id;
You can read more about trackBy here: https://dev.to/rfornal/angular-is-trackby-necessary-with-ngfor-594e

Get calling table from column option

I have a few Bootstrap table on one page. Each table has some data attributes (like data-link="test-page" and so on). Besides that, one column of each Bootstrap table uses a column formatter, using data-formatter="actionFormatter". However, I want to get the current table data attributes when actionFormatter is called, so based on the data attributes I can return a string.
Both this and $(this) return an Object, which doesn't work. $(this).closest('table').data() doesn't work either, while I expected that one to be the most true.
Here's the code I use:
<th data-field="actions" data-formatter="actionFormatter" data-events="actionEvents">Actions</th>
this returns a JSON object with the row properties, and $(this).closest('table').data(XXX) return undefined. I expected it to return an array with all the data attributes.
Is there any way to get the current processing table from within the formatter?
Example code:
<!-- table 1 -->
<table
data-actions="edit,remove"
data-url="some/url"
>
<thead>
<tr>
<th data-formatter="actionFormatter">Actions</th>
</tr>
</thead>
</table>
<!-- table 2 -->
<table
data-actions="edit,remove"
data-url="some/url"
>
<thead>
<tr>
<th data-formatter="actionFormatter">Actions</th>
</tr>
</thead>
</table>
// actionFormatter:
function actionFormatter(value, row, index, field) {
// get data-actions from the right table somehow,
// and return a string based on data-url/other
// data attributes
}
It seems that when the action formatter is called, the execution context this is an object with all the bootstrap table row associated data as well as all the data-* attributes of the row.
Taking that into account you can add an id to each table and a data-table-id attribute to your rows like:
<table
id="table-1"
data-actions="edit,remove"
data-url="some/url"
>
<thead>
<tr>
<th data-formatter="actionFormatter" data-table-id="table-1">Actions</th>
</tr>
</thead>
so that in your formatter you can retrieve the table DOM Element by using that id:
function actionFormatter(value, row, index, field) {
// get data-actions from the right table somehow,
// and return a string based on data-url/other
// data attributes
var data = $('#' + this.tableId).data();
}

AngularJS How Can I Ignore hidden table rows in ng-repeat but keep the increment?

I have an ng-repeat that generates a series of table rows. One column is titled "task status" and if the status is displaying "done", I see no reason in showing it, as the job has been completed.
I used ng-show = values != 0; this initially worked until I added an increment to number the tasks.
What I have found was that the data = "done" were not totally removed from the DOM and still regstering in the list disrupting the increment. See image below:
list increment disruption
So the rows 2 and 3 are data that equal "done". What can I do to ignore them?
Here is my markup:
<table class="backlog table table-bordered table-striped" width="100%" border="0" cellpadding="0" cellspacing="0" summary="Our Jira Backlog">
<tbody>
<tr>
<th>Priority</th>
<th>Task Priority Score</th>
<th>Task Summary</th>
<th>Due date</th>
<th>Task Status</th>
</tr>
<tr ng-repeat="issue in issues | orderBy: '-fields.customfield_12401'" ng-if="issue.fields.status.statusCategory.name != 'Done'">
<td>{{ $index + 1 }}</td>
<td>{{ issue.fields.customfield_12401 }}</td>
<td>{{ issue.fields.summary }}</td>
<td>{{ issue.fields.customfield_13700 }}</td>
<td>{{ issue.fields.status.statusCategory.name }}</td>
</tr>
</tbody>
</table>
So anything that comes from "issue.fields.status.statusCategory.name" needs to be ignored so the Priority (First Column) goes, 1,2,3,4,5 etc and not display "done" the Task Status Column.
I think the best way to handle this situation is to the filter the array first. You can filter the array all in the ng-repeat expression, just retain a variable of the filter output that you can reference in the template (if you need to).
Check out the answer to this SO question: AngularJS - how to get an ngRepeat filtered result reference
edit: to clarify I think you should change your ng-if into a custom filter, and apply it before the ng-repeat indexes the filtered array:
<tr ng-repeat="issue in (filteredIssues = (issues | orderBy: '-fields.customfield_12401' | filter: customFilterFunction))">
<td>{{ $index + 1 }}</td>
You should be able to keep your ng-if as a filter and also keep incrementing your $index by simply moving your $index to the ng-repeat as track by:
<tr ng-repeat="issue in (issues | orderBy: '-fields.customfield_12401') track by $index" ng-if="issue.fields.status.statusCategory.name != 'Done'">
Your $index should match the index of the for-each being performed, even with the ng-if implemented.
UPDATE: I believe I interpreted your question wrong. You want your $index to skip you "done" rows? That wasn't clear...
As #Ericson578 suggested, you should make a custom filter to remove the "done" rows from your array.
<tr ng-repeat="issue in ((issues | removeDoneRows) | orderBy: '-fields.customfield_12401') track by $index" >
...and your removeDoneRows filter (please come up with a better name for this):
angular.module('myFilter', [])
.filter('removeDoneRows', function() {
return function(arr) {
var retval = [];
for(var i = 0; i < arr.length; i++){
if(arr[i].fields.status.statusCategory.name.toLowerCase() != "done")
retval[retval.length] = arr[i];
}
return retval;
};
})

AngularJS ng-repeat column aware table

I have the following model: Item : {data: String, schedule: DateTime, category: String}
I need to display a report of this data in the following way:
<table>
<tr>
<th>Time Range</th>
<th>Category 1</th>
<th>Category 2</th>
<th>No Category</th>
</tr>
<tr>
<td>6:00 - 2:30</td>
<td>3</td>
<td>5</td>
<td>7</td>
</tr>
</table>
So I will need to compile and filter the list of Items into the time ranges and display the totals based on categories. How can I accomplish this with angular and tell which column I am in (and therefore choose the right value to display) as the categories are dynamic.
EDIT: The table will display a summary of all Items. So you'll have take all items and compile it into the form Time Range, # In Category 1, # In Category 2, # In No Category. The only thing that will be set by me is the time ranges.
I plan to store this data in a hash map where the keys are the category names, but I need to know which column I am in as the categories are dynamic. The categories come from the database (a user putting these items in).
Basically you need to do two things. Group list by category and display grouped list. See JsFiddle: https://jsfiddle.net/vittore/mmvxbcjx/4/
In order to group list you can use array.reduce. Convenient data structure for that would be hash of hashes, ie
var hash = {
'6:30': {
category1: 5
}
}
(Say you are grouping datetime based on time with an hour step.)
In order to get structure like that with reduce you will do:
var myList = [{},....];
$scope.grouped = list.reduce(function(a, d) {
var hours = d.schedule.getHours();
if (!(hours in a)) a[hours] = {}
var timeSlot = a[hours]
timeSlot[d.category || 'no category' ] =
1 + ( timeSlot[d.category || 'no category'] | 0)
return a;
}, {})
After you've got desired structure you need to do nested ng-repeat with angular:
<tr>
<th>time slot</th>
<th ng-repeat='c in categories'>{{ c }} </th>
<th>no category</th>
</tr>
<tr ng-repeat='(k,v) in grouped'>
<th>{{ k }} : 00</th>
<td ng-repeat='c in categories'>{{ v[c] }} </td>
<td>{{ v['no category'] }}</td>
</tr>

Categories