angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.32k stars 6.72k forks source link

[Table] Add a footer row (e.g. totals) #7548

Closed Knoxvillekm closed 6 years ago

Knoxvillekm commented 6 years ago

Bug, feature request, or proposal:

feature reques

What is the expected behavior?

Add total row:

  <md-table [dataSource]="dataSource">

    <!-- Name Column -->
    <ng-container mdColumnDef="name">
      <md-header-cell *mdHeaderCellDef> Name </md-header-cell>
      <md-cell *mdCellDef="let row"> {{row.name}} </md-cell>
      <md-total-cell *mdTotalCellDef> Total </md-total-cell>
    </ng-container>

    <!-- Count Column -->
    <ng-container mdColumnDef="count">
      <md-header-cell *mdHeaderCellDef> Count </md-header-cell>
      <md-cell *mdCellDef="let row"> {{row.count}} </md-cell>
      <md-total-cell *mdTotalCellDef> {{dataSource.sumBy('count')}} </md-total-cell>
    </ng-container>

    <md-header-row *mdHeaderRowDef="displayedColumns"></md-header-row>
    <md-row *mdRowDef="let row; columns: displayedColumns;"></md-row>
    <md-total-row *mdTotalRowDef="displayedColumns"></md-total-row>
  </md-table>

Result:

Name       Count 
row 1       1        
row 2       2
Total       3

Or something like md-pagination.

  <md-table [dataSource]="dataSource">

    <!-- Name Column -->
    <ng-container mdColumnDef="name">
      <md-header-cell *mdHeaderCellDef> Name </md-header-cell>
      <md-cell *mdCellDef="let row"> {{row.name}} </md-cell>
    </ng-container>

    <!-- Count Column -->
    <ng-container mdColumnDef="count">
      <md-header-cell *mdHeaderCellDef> Count </md-header-cell>
      <md-cell *mdCellDef="let row"> {{row.count}} </md-cell>
    </ng-container>

    <md-header-row *mdHeaderRowDef="displayedColumns"></md-header-row>
    <md-row *mdRowDef="let row; columns: displayedColumns;"></md-row>
  </md-table>

  <md-table-total [dataSource]="dataSource">

    <!-- Name Column -->
    <ng-container mdColumnDef="name">
      <md-total-cell *mdTotalCellDef> Total </md-total-cell>
    </ng-container>

    <!-- Count Column -->
    <ng-container mdColumnDef="count">
      <md-total-cell *mdTotalCellDef> {{dataSource.sumBy('count')}} </md-total-cell>
    </ng-container>
  </md-table-total>
willshowell commented 6 years ago

See https://github.com/angular/material2/issues/6570

jelbourn commented 6 years ago

@andrewseguin it's probably worth talking about a more convenient shortcut for adding a summary row at some point

andrewseguin commented 6 years ago

To be clear - this is meant to be a sticky footer at the bottom rather than a "last row"?

jelbourn commented 6 years ago

I think so, but we should collect a few use-cases before committing to anything.

andrewseguin commented 6 years ago

Since the table itself doesn't have knowledge of its scrollable container (its just a repeat of rows), I wonder if this would be out of scope. We should definitely chat more about this (and the sections feature seems similar actually).

In the meantime, a solution to this would be to create a new table and append it right after the initial table. Hide the header row and keep the column IDs similar (to re-use any column-specific class styles you might have).

This way, the table can be its own scrollable and the footer could just append right after the scrolling container.

A separate data source should be provided since the data is unique.

Here's a proof of concept https://plnkr.co/edit/vZz0ZuMII8Pudd0zMewC?p=preview

Unfortunately most browsers push content left when a scrollbar appears, so it's likely that the columns will be misaligned for most users :(

Edit: Reminds me of a simple solution for sticky headers as well =P

willshowell commented 6 years ago

I was making this to supplement https://github.com/angular/material2/issues/6570#issuecomment-334289406, but figure I'll add it here as an alternative solution in the mean-time,

https://stackblitz.com/edit/material2-beta12-fkcu2t

cc @Tempus35

timwright35 commented 6 years ago

@willshowell Thanks!

pantonis commented 6 years ago

This is a very important feature for a datatable. Since datatables are used mostly for showing numeric tabular data, totals is a must feature.

pantonis commented 6 years ago

@andrewseguin Why is this minor? Is there any workaround that works without any issues? From what you said your workaround above does not work well. :)

timwright35 commented 6 years ago

I was just wondering if the discussion was still happening on this or if this is dead in the water. Could really use this in my current project.

pantonis commented 6 years ago

Such a nice table with lot of features but one of the most critical features of tabular data is missing. This missing feature prevents me from using material datatable.

padilla-jm commented 6 years ago

I agree, this is something that is used all the time with tables.

andrewseguin commented 6 years ago

Definitely still on our radar. Will likely call this something like mat-footer-row and implement it similarly to the header and data rows.

Unfortunately its minor compared to other features. Just gotta find some priority with respect to all the features we want on the table. Right now my biggest priority is reducing the pain in setting up the table (e.g. easier to make data sources, allow data arrays for simplicity, make it easier to define columns)

pantonis commented 6 years ago

I think it already became quite simple to setup the table compared to previous versions. Never mind though didnt have morentime to wait switched to another table already. Keep up the good work

GoLucas commented 6 years ago

@willshowell how can i use your solution on my async datasource ?

  onChange(event) {
    this.dataSource = new ReservationDataSource(this.paymentsData, event.value);
}
export class ReservationDataSource extends DataSource<any> {
  reservation_id: any;
  constructor(private paymentsData: UserPaymentsService, reservation_id: any) {
    super();
    this.reservation_id = reservation_id;
  }
  connect(): Observable<Payment[]> {
    return this.paymentsData.httpGetPayments(this.reservation_id); 
  }
}
willshowell commented 6 years ago

@GoLucas you could try something like this:

interface Payment {
  date: Date;
  cost: number;
}

interface PaymentTotal {
  total: number;
}

// ...

connect(): Observable<(Payment | PaymentTotal }[]> {
  return this.paymentsData.httpGetPayments(this.reservation_id)
    // Append a 'total' row at the end
    .map(payments => {
      const paymentsSum = payments.reduce((accum, curr) => accum + curr.cost), 0);
      return [...payments, { total: paymentsSum }];
    });
}
GoLucas commented 6 years ago

@willshowell i have one more question how can i get isLastRow like in example below https://stackblitz.com/edit/material2-beta12-fkcu2t?file=app%2Fapp.component.ts

willshowell commented 6 years ago

@GoLucas a simple way would be to save it in the datasource:

// In your datasource
public totalRowIndex;

connect(): Observable<(Payment | PaymentTotal }[]> {
  return this.paymentsData.httpGetPayments(this.reservation_id)
    // Append a 'total' row at the end
    .map(payments => {
      const paymentsSum = payments.reduce((accum, curr) => accum + curr.cost), 0);
      return [...payments, { total: paymentsSum }];
    })
    .do(payments => this.totalRowIndex = payments.length - 1);
}
// In your component class
isLastRow = (data, index) => index === this.dataSource.totalRowIndex;
GoLucas commented 6 years ago

in my case still dont work image

i im initialize datasource on change event in select

  onChange(event) {
    const isLastRow = (data, index) => index === this.dataSource.totalRowIndex;
    this.dataSource = new ReservationDataSource(this.paymentsData, event.value);
}

when i move isLastRow on the top under Class i have one additional empty row. and this is my html:

image

dushkostanoeski commented 6 years ago

Is there any progress on this matter, or some ETA?

andrewseguin commented 6 years ago

@GoLucas

Your component should have this as a property: isLastRow = (data, index) => index === this.dataSource.totalRowIndex;

E.g.


export class MyComponent {
  ...
  isLastRow = (data, index) => index === this.dataSource.totalRowIndex;
  ...
}
rain01 commented 6 years ago

This works for me for detecting the last row...

<mat-row *matRowDef="let row; columns: displayedColumns; let last = last;" [ngClass]="{'last-table-row': last}"></mat-row>

Except it's still useless with sorting. Really could use footer right about now.

andrewseguin commented 6 years ago

@rain01 Nearly there, got a PR open that will add a footer row #10330

rain01 commented 6 years ago

Thanks, yup. I saw there's The Travis CI build failed. Right now I'm using a workaround which works great for simpler things with sorting. Haven't tested it with pagination yet though.

Just put this below mat-table.

<div class="mat-table" role="grid">
    <div class="mat-header-row" role="row">
        <div class="mat-header-cell" role="columnheader">{{ row.name }}</div>
        <div class="mat-header-cell" role="columnheader">{{ row.age }}</div>
        <div class="mat-header-cell" role="columnheader">{{ row.count }}</div>
    </div>
</div>
sivahanuman commented 6 years ago

Hi any working total footer column on https://stackblitz.com example with paging and sorting

sivahanuman commented 6 years ago

Hi Mr.rain, Can you post the working solution on stackblitz.

angular-automatic-lock-bot[bot] commented 5 years ago

This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.