How do I improve performance when constructing a grid in Angular? - html

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;
}

Related

Angular - How to display single "no results" message on no results

I'm having trouble coming up with a way to show my "no results" div element. Basically, I have a list component containg order timeline section components, each one of these section contains order components. Like so:
My orders-list.component.html (check bottom div):
<div class="list-container" [ngClass]="{section: isDeliverySlotsActive === false}">
<label class="list-header" *ngIf="isDeliverySlotsActive === true" style="margin-top: 1.625rem">DELIVERY SLOTS ORDERS</label>
<div [ngClass]="{section: isDeliverySlotsActive === true}" *ngFor="let date of timelines">
<app-orders-list-section
[orders]="orders"
[timeline]="date"
[isDeliverySlotsActive]="isDeliverySlotsActive"
[searchTerm]="searchTerm"
></app-orders-list-section>
</div>
</div>
/* I want to show the below div when there are no results for the search */
<div id="no-results">
<img src="../../../assets/my-orders/no-results.png" alt="No Results" style="margin-top: 6.063rem; margin-bottom: 2.837rem;">
<label class="no-results-text">COULDN'T FIND ANYTHING</label>
<label class="no-results-text weight-medium">Search by order number or customer</label>
</div>
For each section, a filtering method is applied when the user searches for an order using the search bar. If the search term does not correspond to an order in a section, the order is not displayed for that section. If there are no results for that section the section header is also not displayed.
My orders-list-section.component.html:
<div *ngIf="filteredSectionOrders.length > 0">
<label
*ngIf="isDeliverySlotsActive === true"
[ngClass]="{ slots: isDeliverySlotsActive === true }">
{{ timeline | addSectionDateFormat }}
</label>
</div>
<div *ngFor="let order of filteredSectionOrders">
<app-orders-list-item
[order]="order"
[timeline]="timeline"
></app-orders-list-item>
</div>
My filter method in the section component:
filterSectionOrders(searchString: string){
if(!searchString) return;
if(this.hasNumbers(searchString)){
this.filteredSectionOrders = this.filteredSectionOrders.filter(order => order.order_num.toString().indexOf(searchString) !== -1);
}
else{
this.filteredSectionOrders = this.filteredSectionOrders.filter(order => {
if(order.first_name && order.last_name){
let fullName = order.first_name + " " + order.last_name;
if(fullName.toLowerCase().indexOf(searchString.toLowerCase()) !== -1){
return order;
}
}
})
}
}
Given that I apply this filter to each section and not to the list as a whole, how can I find out when there are 0 total results so I can show only one (not for each section) div element with a "no results found" message?
Thank you in advance.
You can easily use *ngIf;else link to ngIf from angular inside your HTML
I am not sure where do you use filteredSectionOrders, because it is not shown in your html, but let's assume your app-orders-list-section has some HTML logic where you use *ngFor to loop through orders and show it properly
so, I guess your code looks something like this
<div class="order" *ngFor="let order of filteredSectionOrders">
<img/>
<p>
{{ order.first_name + ' ' + order.last_name }}
</p>
</div>
This is simplified html how I assume it looks like.
What you can do is next:
<ng-template *ngIf="filteredSectionOrders.length > 0; else noResultsBlock">
// here you insert your code to render orders
<div class="order" *ngFor="let order of filteredSectionOrders">
<img/>
<p>
{{ order.first_name + ' ' + order.last_name }}
</p>
</div>
</ng-template>
<ng-template #noResultsBlock>
<p> No results </p>
</ng-template>
So, this would simple solution
If you want to improve it even more, it would be better to have a new variable, lets say areThereResults, which you will set to true or false, at the end of your method filterSectionOrders, based on filterSectionOrders.length. Then, you would use this new variable inside *ngIf check, instead of filterSectionOrders.length > 0.
Reason for using boolean variable instead of using actual array is detection changes, and will anguar re-render UI inside *ngIf. You can read more about it on Angular documentation, just search for detection changes.

Angular : how to create the row dynamically in angular

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"

How to assign a starting index value while using ngFor in angular

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 = '';
}
}

Update table cell value dynamically using angular6

I am working with an angular 6 application, in the HTML template I have some code as per below, just showing the table cell part of the array, also the table is built using divs.
<div class='table_small'>
<div class='table_cell'>Status</div>
<div class='table_cell'>
<p class="status" >{{incomingData.status}}</p>
</div>
</div>
Please note here that "data" is an array (*ngFor) and is being used in row data and there are multiple data in the table.
Now I have a situation wherein there is a button inside the table rows to cancel the particular order, when the user clicks in, a pop up/modal asks for user confirmation, if the user opts for 'Yes' it would change the status field value to "cancellation is in process" temporarily before it hits the service, once there is a successful response from the customer it would change the station to "cancelled".
I am really not sure how to do the cancellation within the table cell here, if anyone can give insight on this please do.
Thanks
You could pass the element to the function and edit its status:
<div class='table_small'>
<div class='table_cell'>Status</div>
<div class='table_cell'>
<p class="status" >{{incomingData.status}}</p>
</div>
<div class='table_cell'>
<button (click)="showCancelModal(incomingData)"> Cancel</p>
</div>
</div>
And then in the component something like this:
showCancelModal(incomingData) {
// logic for showing modal and retrieving user response
if( response === 'yes') {
incomingData.status = 'Cancel in progress';
yourService.cancel(incomingData)
.pipe( finally(() => incomingData.status = 'Cancelled') )
.subscribe();
}
}

Increment ng-repeat index value in Html

I want to increment value of $index in ng-repeat. For example:
<div ng-repeat:x in ProductList">
<label>{{x}}</label> // if x = 1
x++; // this is the line i need
<label style="color:red">{{x}}</label> // x index should be equal to 2
x++; // this is the line i need
<label style="color:blue">{{x}}</label> // x index should be equal to 3
</div>
in the second time the loop runs, the x index needs to be '4'
You can simply do following:
<div ng-repeat="product in ProductList">
<label>{{$index}}</label>
<label style="color:red">{{$index + 1}}</label>
</div>
If I understand your question correctly (which is hard to understand), you want to perform different actions for even and odd indexes in your ng-repeat. You could use the special properties $even and $odd from ng-repeat.
<div ng-repeat="x in ProductList">
<label ng-if="$odd">{{x}}</label>
<label ng-if="$even" style="color:red">{{x}}</label>
</div>
If this does not help you, try to give an example of what output you are expecting.
You are trying to print index value in diff element for each item in your array,
what i will suggest is to create a custom directive and use it on different element here is "label", that we increment the count of index.