Utilizing *ngfor in Angular 7 to Group and Display Data

I currently have incoming data structured like this in my Angular application

Visit this link to see the data format

https://i.stack.imgur.com/jgEIw.png

What is the best way to group and sort the data by State and County, and present it in a table as shown below?

https://i.stack.imgur.com/azRyC.png

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  data = [
    { state: 'MN', county: '1', item: 0.297 },
    { state: 'MN', county: '1', item: 0.04 },
      { state: 'CA', county: '2', item: 0.019 },
    { state: 'MN', county: '1', item: 0.0374 }, 
    { state: 'CA', county: '2', item: 0.037 }
]
}
 <table >
        <tr>
          <th>State</th>
          <th>County</th>
          <th>Item</th>
        </tr>
        <ng-container *ngFor="let dataitem of data">
        <tr>
          <td>{{dataitem.state}}</td>
          <td>{{dataitem.county}}</td>
          <td>{{dataitem.item}}</td>
        </tr>
        </ng-container>
    </table>

Answer №1

If your data is already ordered, you can utilize the 'let i = index' method to compare if the previous data item is the same using a conditional operator.

<ng-container *ngFor="let dataitem of data; let i=index">
<tr>
  <td>{{i > 0 && data[i-1].state == dataitem.state ? '' : dataitem.state}}</td>
  <td>{{i > 0 && data[i-1].county == dataitem.county ? '' : dataitem.county}}</td>
  <td>{{dataitem.item}}</td>
</tr>
</ng-container>

Answer №2

Utilize the power of lodash to organize your data into separate arrays.

Learn more about lodash's groupBy method here!

import { groupBy } from 'lodash-es'
const countries = groupBy(data, 'country')

You can display multiple tbody elements in your table

<tbody *ngFor="let country of countries">...</tbody>

Answer №3

By following the instructions in this helpful answer, you can preprocess your data or use a pipe to generate nested objects.

Here is an example inspired by the linked solution:

key = 'state';
data = data.reduce((data, x) => {
    (data[x[key]] = data[x[key]] || []).push(x);
    return data;
  }, {});

The above code snippet should result in something like this:

data = [
    { 'MN': [
        { county: '1', item: 0.297 }
        ....
    ]}
]

Alternatively, if your list is sorted, you can employ clever *ngIf and index logic. Check out more details here. Here's an example of how to do it:

<div *ngFor="let item of list; let i = index;">
  <div *ngIf="i > 0 && item.state !== list[i-1].state">{{item.state}}</div>
</div>

Answer №4

Check out this fully functional solution along with a Stackblitz Demo demonstrating cell merging capabilities.

To achieve the desired effect, you need to calculate a row span for each item and assign it to the rowspan attribute of the respective td. Additionally, conditionally render the td to only display it for the first occurrence of each state.

An effective strategy involves preprocessing an array sorted by state and county, incorporating added span properties for both states and counties.

Determining these span properties involves tallying the child count for each state and county within that state by filtering the original data array.

The objective is to structure the array as follows:

[
  {state: "CA", county: "2", item: 0.019, stateSpan: 3, countySpan: 2},
  {state: "CA", county: "2", item: 0.037, stateSpan: 0, countySpan: 0},
  {state: "CA", county: "3", item: 0.14, stateSpan: 0, countySpan: 1},
  {state: "MN", county: "1", item: 0.297, stateSpan: 4, countySpan: 3},
  {state: "MN", county: "1", item: 0.04, stateSpan: 0, countySpan: 0},
  {state: "MN", county: "1", item: 0.0374, stateSpan: 0, countySpan: 0},
  {state: "MN", county: "3", item: 0.14, stateSpan: 0, countySpan: 1}
]

Referencing the code snippet below:

 <table>
    <tr>
      <th>State</th>
      <th>County</th>
      <th>Item</th>
    </tr>
    <tr *ngFor="let item of dataExt">
      <td [attr.rowspan]="item.stateSpan" *ngIf="item.stateSpan">{{ item.state }}</td>
      <td [attr.rowspan]="item.countySpan" *ngIf="item.countySpan">{{ item.county }}</td>
      <td>{{ item.item }}</td>
    </tr>
</table>
export class AppComponent  {
  data = [
    { state: 'MN', county: '1', item: 0.297 },
    { state: 'MN', county: '1', item: 0.04 },
    { state: 'MN', county: '3', item: 0.14 },
    { state: 'CA', county: '2', item: 0.019 },
    { state: 'MN', county: '1', item: 0.0374 }, 
    { state: 'CA', county: '2', item: 0.037 },
    { state: 'CA', county: '3', item: 0.14 }
  ];

  dataExt: any[] = [];

  constructor() {
    this.processData();
  }

  private processData() {
    const statesSeen = {};
    const countiesSeen = {};

    this.dataExt = this.data.sort((a, b) => {
      const stateComp = a.state.localeCompare(b.state);
      return stateComp ? stateComp : a.county.localeCompare(b.county);
    }).map(x => {
      const stateSpan = statesSeen[x.state] ? 0 :
        this.data.filter(y => y.state === x.state).length;

      statesSeen[x.state] = true;

      const countySpan = countiesSeen[x.state] && countiesSeen[x.state][x.county] ? 0 :
        this.data.filter(y => y.state === x.state && y.county === x.county).length;

      countiesSeen[x.state] = countiesSeen[x.state] || {};
      countiesSeen[x.state][x.county] = true;

      return { ...x, stateSpan, countySpan };
    });
  }
}

The resulting table can be viewed here:

https://i.stack.imgur.com/oq3va.png

Answer №5

Maybe try a solution similar to the following:

interface Data {
  category?: string;
  subcategory?: string;
  value: number;
}

private organizeData(inputData: any[]): Data[] {
  // Arrange data alphabetically
  const sortedData = inputData.sort((a, b) => {
    if(a.category < b.category) { return -1; }
    if(a.category > b.category) { return 1; }
    return 0;
  });

  // Keep only one entry per distinct category value
  return sortedData.map((element, index) => {
    if (index === 0 || sortedData[index-1].category !== element.category) {
      return element;
    } else {
      return {
        item: element.item
      };
    }
  });
}

This approach should have a time complexity of `O(log n)` and provide results in the format shown below:

[
  { category: 'A', subcategory: '1', value: 0.123 },
  { value: 0.456 },
  { category: 'B', subcategory: '2', value: 0.789 },
  ...
]

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The removeClass method does not affect the class attribute when using $(this).attr('class'), but only when using $(this).attr('id')

I am currently facing an issue with reducing the size of my jQuery code. The main function is to validate a form - if empty, I want to add a class of 'highlight' or remove it using addClass('highlight') and removeClass('highlight&a ...

Whenever a new entry is made into the textfield, the onChange feature triggers a reset on the content within the textfield

I'm experiencing an issue while creating a SignUp authentication page with Firebase. Every time I try to input text in the text field, it gets reset automatically. I have been unable to identify the root cause of this problem. It seems to be related t ...

Unable to integrate BokehJS with Angular8

Here is the error log that appeared in the browser: AppComponent.html:1 ERROR TypeError: FlatBush is not a constructor at new SpatialIndex (vendor.js:90501) at AnnularWedgeView.push../node_modules/bokehjs/build/js/lib/models/glyphs/xy_glyph.js.XYG ...

Substitute the information in the table with new data

I am working with an HTML table that has 5 columns, one of which contains dates. I need to convert the date format only if the data is not empty. To achieve this, I am utilizing moment.js for date formatting. While the date format conversion works perfect ...

What causes a union with a conditionally assigned property to lead to more relaxed types than anticipated?

Take a look at this TypeScript code snippet: const test = Math.random() < 0.5 ? { a: 1, b: 2 } : {}; Based on the code above, I would assume the type of object 'test' to be: const test: { a: number; b: number; } | {} This is the most str ...

Error message: Angular 7 - Running out of memory due to JavaScript heap

When attempting to run the ng serve command in my Angular 7 application, I encountered an error message stating "JavaScript heap out of memory." After researching various responses on Stack Overflow, it became clear that this issue stems from inadequate m ...

Discover the most frequent value in an array by utilizing JavaScript

My array contains repeating values: [0, 1, 6, 0, 1, 0] How can I efficiently determine the highest frequency of a specific value being repeated? For example, in this array, I would like the script to return 3 since the number 0 repeats most frequently a ...

Using the IE method getelementbyid to target an object within the document

Is there a way to use getElementById to access an object that already exists in the document? I am specifically trying to target the element "test" which is nested within parentDiv1. While this code works in Firefox, it's not functioning properly ...

Adding form input fields in real-time by clicking the add button

Currently, I am looking to develop a party hosting application using Ionic 2. My main goal is to have the ability to add form input fields for guests dynamically by clicking on an "add" button. Do you have any suggestions on how this can be achieved in I ...

Implementing a feature to dynamically add multiple markers on Google Maps

Currently, I am utilizing the .text() method to extract latng from the html. <div class="latlng"> -33.91722, 151.23064</div> <div class="latlng"> -32.81620, 151.11313</div> As a result, I am using $(latlng).text() to retrieve the ...

Easily refresh multiple select options by using the ajax updater function in prototype

After carefully reviewing the documentation for Ajax.Updater(), I noticed that the first argument to the constructor should be container (String | Element) – The DOM element whose contents will be updated as a result of the Ajax request. This can eith ...

Removing a faded out div with Vanilla JavaScript

I am struggling with a JS transition issue. My goal is to have the div automatically removed once it reaches opacity 0. However, currently I need to move my mouse out of the div area for it to be removed. This is because of a mouseleave event listener that ...

Displaying multiple lines in an alert box using Angular 8

I need assistance in displaying an alert message when the user selects a checkbox. We have a shared alert service component that is being utilized by every module. My current code snippet is as follows: if(this.checkboxvalue) { this.al ...

When a jQuery click event is triggered, the event.target will return the child element that was clicked within the

When I have a jQuery click event assigned to a hyperlink that contains an image, each with separate ids, I expect clicking the hyperlink to trigger the code event.target.id, returning the hyperlink's id. However, it actually returns the image's i ...

Sticky box fails to maintain position as header scrolls

I am looking to create a Sidebar that sticks to the window while scrolling, but stops when it reaches the footer. I have managed to get it partially working, but there is a small issue that I can't seem to solve. Test it live here: Everything seems ...

Steps to enable navigation to external pages from a ReactJS app

I am working on a simple ReactJS application: [Demo] [Source] I am trying to implement navigation within the app from external sources without refreshing the web page. This functionality should be similar to using this.props.history.push(...). /public/i ...

Using jQuery and PHP to send a dynamic form through AJAX

I'm currently working on a pet registration form where users can add new pets. When a user clicks the "add pet" button, I use jQuery to clone the pet section of the form and give each cloned section an id like #pet-2, #pet-3, and so on. Although my ...

An easy guide to dynamically assigning a property using jQuery

I am currently utilizing the toastr plugin and I would like to dynamically set the options using a JSON object that is retrieved from an AJAX call. I am encountering some difficulties in setting the options property and value programmatically. Below is a s ...

I need some help with adjusting the number of rows shown per page in MaterialReactTable

I've been utilizing MaterialReactTable and my goal is to display only 5 items on each pagination page. Despite setting muiTablePaginationProps, I still see 10 items per page. How can I resolve this issue? <MaterialReactTable columns={columns} ...

Utilizing $http (REST) to send information from a form

Struggling to leverage Angular for CRUD processes, especially encountering difficulties with POST requests to the server. This is my controller: angular.module('myModule').controller("ListingCtrl", function($scope, posts) { $scope.addProje ...