The code below is in a view of main-category.component
<div>
<div *ngFor="let category of categories;">
<app-subcategory>
[name]="category.name">
</app-subcategory>
</div>
</div>
I'd like generate a table with 4 columns and x rows depending the number of items in categories. Each cell contain the component app-subcategory.
I don't see the right way to do this. Do you have idea ?
Thanks,
#Pipe({ name: columns })
export class ColumnsPipe implements PipeTransform {
transform(data, numColumns) {
if(!data || data.length == 0) {
return data;
}
var i,j,temparray,chunk = 10;
result = [];
for (i=0,j=data.length; i<j; i+=chunk) {
result.pop(array.slice(i,i+chunk));
}
return result;
}
}
see also https://stackoverflow.com/a/8495740/217408
<div>
<div *ngFor="let row of categories | columns:4">
<div *ngFor="let category of row">
<app-subcategory>
[name]="category.name">
</app-subcategory>
</div>
</div>
</div>
You might want to change the divs into <tr> and <td> to get real rows and columns.
Related
Everytime the route changes (recordTab.Id changes) I have to construct a new grid with five columns and output it.
The following code generates the grid every route change
// used later in html code to generate five columns with *ngFor
this.columns = [0,1,2,3,4]
// the records are filtered based on the id
this.id = recordTab["id"];
//allRecords has 1300 elements in it
this.records= allRecords.filter(record => record.recordTabId == this.id); // filter by recordTabId
// construct grid with 5 columns
// the maximum number of cells per column are 300
for(let i=0; i<5; i++){
this.grid[i] = [];
this.grid[i] = new Array(recordTab["gridCells"]); // number of cells in a column
}
if(this.records){
for(let record of this.records){
// assigning record to its corresponding cell
this.grid[record.column - 1][record.row - 1] = record;
}
}
// has maximum 5*300 entries
// 600 entries filled
// rest is empty to simulate empty cells
console.log(this.grid)
Now I am displaying the grid in the following way:
<div class="grid-container">
<div class="column" *ngFor="let column of columns">
<div class="cell" *ngFor="let record of grid[column]">
<ng-container *ngIf="record">
<div class="record checkboxes" [ngStyle]="{'background-color': record.categorie==1 ? record.rgbFarbeHex : 'white'}" [ngClass]="{'heading-container': record.categorie==1}">
<label [ngClass]="{'heading': record.categorie==1}" [title]="record.name" (contextmenu)="showRecordInformation($event, record)"> <span *ngIf="record.categorie==0"> <input type="checkbox"> </span> {{record.name}}</label>
</div>
</ng-container>
</div>
</div>
</div>
The problem is that the grid takes some time to show up for the grid with 5*300 entries and 600 actually filled entries. How could I improve the performance?
I dont think the css is the problem, that is why I dont show it. If it is necessary, tell me.
You should take a look at trackBy. It will tell your ngFor to only rerender what changed based on what you are tracking. It's hard to tell if it can work in your case but there is no other solution since as soon as the array changes, angular loose track of what's going on and needs to render the *ngFor again.
It would look like that:
<div class="column" *ngFor="let column of columns; trackBy:trackByFn">
trackByFn(index, item) {
return item.id;
}
I am using
<div *ngFor="let item of object | keyvalue">
{{item.key}}:{{item.value}}
</div>
to print the value in html. but I need to print it in form of table
say total we have 8 key value pair, now i want to create the new row after 4th key value pair, such that it should come in this way
a a a a
a a a a
can we do it dynamically, the key value pair can be 12 or 16, multiple of 4
you could convert your structure into a more suitable one and use a nested loop.
This would also lead to a data structure more similar to a table.
something like that
<table>
<tr *ngFor="let item of arr">
<td *ngFor="let td of item">
{{td}}
</td>
</tr>
</table>
and in your components ts file.
let i = 0;
const itemsPerRow = 4;
Object.keys(this.object).forEach((key,index) => {
if(index > 0 && index % itemsPerRow == 0) {
i++;
}
if(!this.arr[i]) {
this.arr[i] = [];
}
this.arr[i].push(this.object[key]);
});
this will convert your structure into an array of objects where every array item represents one row.
Here's a working stackblitz
Just as further info.
You can get the index of your current element in your *ngFor like
*ngFor="let item of items;let i = index"
I have a requirement to always display minimum of 5 rows(5 or more rows) in a table. For example, if there are 2 rows available in Database, I need to display other 3 more rows in UI with empty rows.
Here is what I tried so far:
<div *ngFor="let task of tasks; let i = index">
<div class="rowDiv">{{task.id}}</div>
</div>
Here I want to run the loop from i = tasks.size to i < = 5. So that I have total of 5 rows in UI. How to achieve this?
<div *ngFor=" let i = index">
<div class="rowDiv"></div>
</div>
You can loop over an array of 5 items, and use *ngIf to display an additional row if no data item exists at a given index:
<div *ngFor="let task of tasks">
<div class="rowDiv">{{task.id}}</div>
</div>
<ng-container *ngFor="let i of [0,1,2,3,4]">
<div *ngIf="!tasks[i]">
<div class="rowDiv">This row is empty</div>
</div>
</ng-container>
See this stackblitz for a demo.
you can also add so many rows you need after
<table>
<row *ngFor="let task in task">
</row>
<!--if task.length<5-->
<ng-container *ngIf="tasks.length<5">
<!-use slice:0:5-tasks.length-->
<row *ngFor="let i of [0,1,2,3,4] |slice:0:5-tasks.length">
</row>
</ng-container>
</table>
You don't need to keep this logic in html.
In you class you can do something like this: (suppose you fetch tasks from server)
this.getTasks().subscribe((tasks) => {
const emptyTasks = Array(5).fill({id: 'empty'});
this.tasks = tasks.map((t, index) => t || emptyTasks[index]);
})
This could be better handled in the controller. In case of default change detection strategy, the template is reloaded multiple times without our control or knowledge. So it's better to make sure the tasks variable has atleast 5 elements in the controller rather than to control the flow in the template. You could something like the following in the controller and leave the template unchanged.
for (let i = 0; i < 5; i++) {
if(!this.tasks[i].id) {
this.tasks[i].id = '';
}
}
I am trying to set up a cashier screen.. and basically I need an addToCart function.. pretty simple huh?!
am facing some weird logical error tho.. what I do is on click of an item, I capture it and pass it as a parameter to a function which in turn maps my Bill array to check whether or not the item already exists there.. and if it does it just increases the quantity by one, otherwise, it pushes the item into the array..
It all works well until I delete an item and re-add it into the array.. it keeps it's previous quantity, if it was 5 then it remains with 5 even after deletion.
For a better explanation, here is my code...
This is how I add my items to Bill (cart)...
TypeScript
addToCart(item: SalesScreenItemsModel) {
let itemExists = false;
// tslint:disable-next-line: variable-name
this.Bill.map((ele, _index) => {
if (item.itemId === ele.itemId) {
itemExists = true;
ele.itemQuantity = ele.itemQuantity + 1;
}
return ele;
});
if (itemExists === false) {
this.Bill.push(item);
}
HTML
<div class="col-xl-3 col-lg-4 col-md-4 col-sm-6 col-xs-12" *ngFor="let item of items">
<div class="card mb-3 widget-content bg-arielle-smile item-pic" style="overflow: hidden; padding: 0;">
<div class='box'>
<div class='content'>
<div class="widget-content-wrapper text-white content" style="justify-content: center;">
<div class="widget-content-left text-center">
<img src="{{ item.itemPicture}}" alt="Raised image" class="img-fluid" (click)="addToCart(item)">
</div>
</div>
</div>
</div>
delete function
deleteBillItem(itemIndex: number) {
this.Bill.splice(itemIndex, 1);
}
HTML
<tr *ngFor="let bill of Bill; let i = index">
<th scope="row" class="text-center">{{i + 1}}</th>
<td class="text-center">{{bill.itemName}}</td>
<td class="text-center">{{bill.itemQuantity}}</td>
<td class="text-center">{{ bill.itemPrice * bill.itemQuantity }}</td>
<td class="text-center">
<button class="btn-icon btn-icon-only btn btn-outline-danger" (click)="deleteBillItem(i)"
style="padding: 1px 6px;">
<i class="pe-7s-trash btn-icon-wrapper"> </i>
</button>
</td>
</tr>
Thing is when I log my items array the change to item quantity actually occurs to the main array as well as to the bill... I know this should be simple and that's why it's driving me nuts...
The problem is that you're missing the assignment in map:
this.Bill = this.Bill.map...
But, as a suggestion, you could write a more functional approach:
addToCart(item: SalesScreenItemsModel) {
const itemExists = this.Bill.some(element => element.itemId === item.itemId);
if (itemExists) {
this.Bill = this.Bill.map(element => ({
...element,
itemQuantity: element.itemQuantity + (element.itemId === item.itemId ? 1 : 0)
}));
} else {
this.Bill = [...this.Bill, item];
}
}
And for the remove:
deleteBillItem(itemIndex: number) {
this.Bill = this.Bill.filter((element, index) => index !== itemIndex);
}
Also, Bill isn't the best name for an array/list :)
Try setting the item.itemQuantity to 1 when you add the item
addToCart(item: SalesScreenItemsModel) {
// tslint:disable-next-line: variable-name
const itemIndex=this.Bill.find((billItem) => item.itemId == billItem.itemId);
if(itemIndex == -1){
item.itemQuantity=1;
this.Bill.push(item);
return;
}
this.bill[itemIndex].itemQuantity+=1;
}
The list in question is a table generated by a reactive angular form which does not have a specific ID. Following code is used to generate the list in angular part:
<p-table id='paragraphList' *ngIf="paragraphsObs | async; else loading"
[value]="paragraphsObs | async"
selectionMode="single" (onRowSelect)="select($event)"
scrollable="true">
<ng-template pTemplate="header">
<tr> ...header... </tr>
</ng-template>
<ng-template pTemplate="body" let-paragraph let-rowData>
<tr [pSelectableRow]="rowData">
<td width="15%">{{paragraph.cell1}}</td>
<td width="10%">{{paragraph.cell2}}</td>
<td width="31%">{{paragraph.cell3}}</td>
<td width="11%">{{paragraph.cell4 | dateTransform: helperService.MM_DD_YYYY_HH_MM_A_Z_DATE_PATTERN}}
</td>
<td width="11%">{{paragraph.cell5}}</td>
<td width="11%">{{paragraph.cell6 | dateTransform: helperService.MM_DD_YYYY_HH_MM_A_Z_DATE_PATTERN}}
</td>
<td width="11%">{{paragraph.cell7}}</td>
</tr>
</ng-template>
</p-table>
The corresponding table generated at the front-end has the following html source:
<p-table _ngcontent-c6="" id="paragraphList" scrollable="true" selectionmode="single" ng-reflect-selection-mode="single" ng-reflect-scrollable="true" class="ng-star-inserted" ng-reflect-value="[object Object],[object Object">
<div class="ui-table ui-widget ui-table-hoverable-rows" ng-reflect-ng-class="[object Object]">
<div class="ui-table-scrollable-wrapper ng-star-inserted">
<div class="ui-table-scrollable-view" ng-reflect-frozen="false">
<div class="ui-table-scrollable-header ui-widget-header">...header...</div>
<div class="ui-table-scrollable-body">
<table class="ui-table-scrollable-body-table" ng-reflect-klass="ui-table-scrollable-body-table" ng-reflect-ng-class="[object Object]">
<tbody class="ui-table-tbody" ng-reflect-template="[object Object]">
<tr _ngcontent-c6="" ng-reflect-data="[object Object]" class="ng-star-inserted">...</tr>
<tr _ngcontent-c6="" ng-reflect-data="[object Object]" class="ng-star-inserted">...</tr>
...
</tbody>
</table>
<div class="ui-table-virtual-scroller"></div>
</div>
</div>
</div>
</div>
</p-table>
I want to reach to those inner elements and get them as a list. I have tried using class names with element and all locators, to get the elements but to no avail. Then I tried using tag names to reach to those elements but that too doesn't seem to work.
This following small snippet returns 0 for the count of elements that I try to obtain from the list.
element(by.id('paragraphList')).element(by.css('.ui-table-scrollable-body-table'))
.all(by.tagName('tr')).count().then(function (result) {
console.log(result);
});
Any help would be appreciated. Thanks
Considering above is your full rendered HTML..
The code below will give an Array of arrays, where each array would be containing texts from all the cells of a row.
Explanation:
The code has three functions,
populateData() - is the driving function where we pass the resolved list of rows.
Then _populateRows() and _populateCells() run recursively to gather the text from the cells. This is also possible to do with a loop (as protractor queues the promises by itself) but I like keeping things clear on my end. _populateRows() recur on rows and _populateCells() recur on cells of each row. (more in comments)
Note This first thing which you should do before implementing this is: check the count() (or .length of resolvedRows) of element.all(by.css('#paragraphList table tbody tr')). As basically this was your original question I believe. Now If you have a count, then you can go with this solution or whatever suites your need.
let allRows = element.all(by.css(`#paragraphList table tbody tr`)); //will have all the rows.
allRows.then((rowsResolved) => {
// now have all the rows
PO.populateData(rowsResolved).then((allData) => {console.log(allData)}) // should be an Array od arrays, each array would be containing texts from all the cells.
// Considering you have a Page Object and added the functions below in the Page Object.
// Page Object is nothing but another class where we keep our utility methods
})
// driving function
populateData(rowsResolved) {
let data = [];
return this._populateRows(0, rowsResolved, data);
}
// calls itself recursively to loop over the rows
private _populateRows(index, rowsResolved, data) {
if (index >= rowsResolved.length) {
let defer = protractor.promise.defer();
defer.fulfill(data);
return defer.promise; // so that it is chainable even if I don't have any rows
}
let cells = element.all(by.css(`#paragraphList table tbody tr:nth-child(${index + 1}) td`));
cells.then((cellsResolved) => {
let cellData = [];
if (cellsResolved.length) {
data.push(cellData);
}
this._populateCells(0, cellsResolved, cellData);
return this._populateRows(index + 1, rowsResolved, data);
})
}
// calls itself recursively to loop over all the cells ofeach row.
private _populateCells(index, cellsResolved, cellData) {
if (index >= cellsResolved.length) {
let defer = protractor.promise.defer();
defer.fulfill(cellData);
return defer.promise; // so that it is chainable even if I don't have any cells(that would be an incorrect structure though, if a row exists then cells have to exist )
}
cellsResolved[index].getText().then((cellValue) => {
cellData.push(cellValue)
});
return this._populateCells(index + 1, cellsResolved, cellData);
}