Here is a fully working solution with a Stackblitz Demo with merging of similar cells.
You have to calculate a row span for each item and bind it to the rowspan attribute of the td. You also have to conditionally render the td to display it only for the first item for each state.
For this, you can create a preprocessed array, ordered by state and by county, with added span properties for states and counties.
To set the span properties, you can count the number of children for each state and each county for the state by filtering the original array.
The goal is to get a array like this:
[
  {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}
]
Here is the code:
 <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 };
    });
  }
}
Here is the resulting table:
