angular / components

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

[Table] Add ability to expand rows on interaction #6095

Closed vibingopal closed 7 years ago

vibingopal commented 7 years ago

Bug, feature request, or proposal:

Hi Team,

Is it possible to extend the table row on click of the record. For example, a table with more records exist and when the user clicked on particular row, the row should expand and show card with the row information in detail as shown below.. image

Material : 2.0.0-beta.8 Angular : 4.0.0

shlomiassaf commented 6 years ago

Turning an accordion into a table might work for some scenarios but I wouldn't recommend it...

First, an accordion is not designed for that... 2nd new feature coming will not work (e.g virtual repeat)

I suggest using the table itself, most of the things you want to do are already doable...

shlomiassaf commented 6 years ago

Posted an updated demo, now working https://stackblitz.com/edit/angular-material2-issue-qatslc

Ukmandal commented 6 years ago

Cannot read property 'toArray' of undefined???

.html <mat-table #table [dataSource]="dataSource" matSort>

        <ng-container matColumnDef="checkbox">
            <mat-header-cell *matHeaderCellDef>
                <mat-checkbox (change)="toggleCheckAll($event)" [indeterminate]="selected.length>0 && employees.length !==selected.length "></mat-checkbox>
            </mat-header-cell>
            <mat-cell *matCellDef="let row">
                <mat-checkbox [checked]="isAllChecked" (change)="onRowSelected($event, row.id)"></mat-checkbox>
            </mat-cell>
        </ng-container>

        <ng-container matColumnDef="id">
            <mat-header-cell *matHeaderCellDef mat-sort-header> Id </mat-header-cell>
            <mat-cell *matCellDef="let row"> {{row.id}} </mat-cell>
        </ng-container>

        <ng-container matColumnDef="userName">
            <mat-header-cell *matHeaderCellDef mat-sort-header> UserName </mat-header-cell>
            <mat-cell *matCellDef="let row"> {{row.userName}} </mat-cell>
        </ng-container>

        <ng-container matColumnDef="firstName">
            <mat-header-cell *matHeaderCellDef mat-sort-header> FirstName </mat-header-cell>
            <mat-cell *matCellDef="let row"> {{row.firstName}} </mat-cell>
        </ng-container>

        <ng-container matColumnDef="lastName">
            <mat-header-cell *matHeaderCellDef mat-sort-header> LastName </mat-header-cell>
            <mat-cell *matCellDef="let row"> {{row.lastName}}</mat-cell>
        </ng-container>

        <ng-container matColumnDef="designation">
            <mat-header-cell *matHeaderCellDef mat-sort-header> Designation </mat-header-cell>
            <mat-cell *matCellDef="let row"> {{row.designation}} </mat-cell>
        </ng-container>

        <ng-container matColumnDef="department">
            <mat-header-cell *matHeaderCellDef mat-sort-header>Department </mat-header-cell>
            <mat-cell *matCellDef="let row"> {{row.department}} </mat-cell>
        </ng-container>

        <ng-container matColumnDef="address">
            <mat-header-cell *matHeaderCellDef mat-sort-header> Address </mat-header-cell>
            <mat-cell *matCellDef="let row"> {{row.address}} </mat-cell>
        </ng-container>

        <ng-container matColumnDef="role">
            <mat-header-cell *matHeaderCellDef mat-sort-header> Role </mat-header-cell>
            <mat-cell *matCellDef="let row"> {{row.role}} </mat-cell>
        </ng-container>

        <ng-container matColumnDef="edit">
            <mat-header-cell *matHeaderCellDef>Edit</mat-header-cell>
            <mat-cell *matCellDef="let row">
                <button mat-raised-button (click)="openConfirmationEdit(id)">Edit</button>
            </mat-cell>
        </ng-container>

        <ng-container matColumnDef="delete">
            <mat-header-cell *matHeaderCellDef>Delete</mat-header-cell>
            <mat-cell *matCellDef="let row">
                <button mat-raised-button (click)="openConfirmationDelete(id)">Delete</button>
            </mat-cell>
        </ng-container>

        <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
        <mat-row class="animated" *matRowDef="let row; columns: displayedColumns; let index=index"   (click)="expandRow(index)"  #cdkrow>
        </mat-row>
    </mat-table>

.ts displayedColumns = ['checkbox', 'id', 'userName', 'firstName', 'lastName', 'designation', 'department', 'address', 'role', 'edit', 'delete']; expandedRow: number; dataSource: MatTableDataSource;

selection = new SelectionModel<number>(true, []);
private containers: QueryList<ViewContainerRef>

@ViewChildren('cdkrow', { read: ViewContainerRef })
@ViewChild(MatSort) sort: MatSort;
@ViewChild(MatPaginator) paginator: MatPaginator;

expandRow(index: number) { if (this.expandedRow != null) { this.containers.toArray()[this.expandedRow].clear(); } if (this.expandedRow === index) { this.expandedRow = null; } else { const container = this.containers.toArray()[index]; const factory: ComponentFactory = this.resolver.resolveComponentFactory(ExpansionComponent); const messageComponent = container.createComponent(factory); messageComponent.instance.childData = this.dataSource.data[index]; this.expandedRow = index; } }

jstuth commented 6 years ago

Greetings, i have a problem with the "matRipple" on a table row. I've tried the code from @shlomiassaf which works fine. But when clicking on a row the entire content of my row falls off the bounds of the table columns. You can see a similar behavior in the plunkr example posted by @shlomiassaf. By removing "matRipple" from mat-row everything works fine. Can i fix this via CSS somehow?

EreckGordon commented 6 years ago

@shlomiassaf thanks for the updated demo! Any idea what would need to be changed to only allow one detail panel open at a time? ie: if one is open, change that opened state to closed, then open what was clicked.

shlomiassaf commented 6 years ago

@EreckGordon That's quite a simple implementation, but a common one so I've added it to the example.

Basically, the directive itself can be extended with some more things commonly required.

The solution is to add an event to the directive, emitting when toggled. In the parent component, keep track of prev toggled row and close on next opened row.

EreckGordon commented 6 years ago

Thanks!

shlomiassaf commented 6 years ago

@Triipaxx I don't understand what you refer to....

Lakston commented 6 years ago

@shlomiassaf I think he's referring to the ripple that is not properly working on your example, the ripple is not contained in the row but ripples of the entire table, see this gif for an idea :

ripple

The problem can be fixed by changing the overflow: initial; to overflow: hidden; in the element-row css rules

I forked your stackblitz with a fix here

Thanks for your example none the less, and for updating it 👍

jstuth commented 6 years ago

Well no, that's not exactly what i was referring to.

@Lakston Your problem can be solved by added the following to your CSS: .mat-row, .mat-header-row { position: relative; }

But your gif shows my issue very well. If you look at the "Name" and "Weight" columns, you can see that the row content of these two columns shifts to the right while the ripple is animating, and jumps back to it's initial position afterwards. In my table it's been shifting to the left. By removing matRipple from mat-row this kind of" content shifting" dos not appear. What could be the reason for this weird issue?

jstuth commented 6 years ago

@Lakston I have to apologize. Your are right, what you have mentioned is an issue of the demo. This is probably related to the material version 6.0.0-rc1.

jstuth commented 6 years ago

Ok, i think i got a fix for my issue. Background information: I have a mat-table with 4 columns (name, city, state, action). The 4th column has a fixed size of 40px, defined using: mat-cell:last-child{ width: 40px; min-width: 40px; max-width: 40px}. This was working until the mat-ripple effect started, than it ignores my CSS and start's to grow. Since this only happens on the clicked row, it looked absolutely odd... I found out, that the action cell had a new generated CSS class attached ".mat-column-action". So i changed my CSS class to the following: .mat-cell.mat-column-action. And now my CSS always applies and the action cell is not growing anymore and the content won't move around.

I hope everyone understands what i was trying to say. English is not my native tongue. ;)

yogeshgadge commented 6 years ago

Another simple implementation: Uses Mat Expansion panel and extends MatRow.

@Component({
  selector: 'my-master-detail-row',
  template: `
    <mat-expansion-panel>
      <mat-expansion-panel-header class="mat-row">
        <ng-container cdkCellOutlet></ng-container>
      </mat-expansion-panel-header>
      <div class="ief-detail-row">
        <ng-content></ng-content>
      </div>
    </mat-expansion-panel>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  exportAs: 'matRow',
  preserveWhitespaces: false,
})
export class MyMasterDetailRowComponent extends MatRow  {
  @HostBinding('class') clazz = my-master-detail-row';
}

and use it like so

    <my-master-detail-row *matRowDef="let row; columns: displayedColumns;">
        <div>this is a test</div>
    </my-master-detail-row>
DVLFocalor commented 6 years ago

@yogeshgadge I tried your solution but i have a problem with the header (not correctly aligned with the data). How did you handle the header?

yogeshgadge commented 6 years ago

@DVLFocalor Add a column to the table in which you want to add this row. ( Use width 64px and no header text.)

arunredhu commented 6 years ago

I implemented the expanded rows with when predicate but sorting on the table behaving unexpectedly like Rows are not maintaining order. I am using Angular 6.0.0 and material 6.0.0

EreckGordon commented 6 years ago

@Triipaxx Since upgrading to Angular6/Material6 I've started experiencing the jank you described (and that the gif shows -- matRipple shifting the contents to the right), and cannot quite understand which piece of css you changed. For reference, this is my css that was working 100% fine in Angular5:

.user-management-table-container {
  display: flex;
  flex-direction: column;
  min-width: 30px;
}

.user-management-table-header {
  min-height: 64px;
  padding: 8px 24px 0;
}

.mat-form-field {
  font-size: 14px;
  width: 100%;
}

.mat-header-cell {
  font-size: 20px;
}

.user-row {
  position: relative;
}

.darken-on-hover:hover {
  background-color: rgba(32, 32, 32, 0.2)
}

what do you recommend changing?

edit: here's the only instance in my html that uses matRipple. If I remove matRipple, problem goes away, but then I lose the nice ripple:

    <mat-row *matRowDef="let row; columns: displayedColumns;" matRipple class="user-row darken-on-hover" [cdkDetailRow]="row" [cdkDetailRowTpl]="expandedDetails" (toggleChange)="onToggleChange($event)"></mat-row>
jstuth commented 6 years ago

@EreckGordon Well, i've managed to get rid of this, by explicitly setting the size of one cell. In my example i have an action cell which contains buttons for various actions on that object represented by the row. By default i'm setting this in CSS by addressing the nth-child, or last-child, element of mat-header-cell or mat-cell. But in this case it wasn't working when the ripple effect is animating. So i've checked the DOM and realized, that each cell (header cell also) has a new CSS class attached, which is generated by the column id and it's designation, e.g. "mat-cell-action". This class was also available during the ripple effect. So i changed my CSS to edit this particular new CSS class and it works properly. Maybe it helps, when you try to define size properties of a single cell (maybe the last one as well), with something more flexible instead of a static width like i do.

literalpie commented 6 years ago

@yogeshgadge 's extension does not work for me. All cell data gets put in the header when I replace a working mat-row with my-master-detail-row

edit: Figured it out! CdkTableModule must be imported for it to work (because CdkCellOutlet is used)

antontruong commented 6 years ago

Hi,

@Lakston I tried your corrected version, but got at 'ng build' these warnings:

WARNING in ./src/app/app.component.ngfactory.js 101:165-173 "export 'ɵf23' (imported as 'i13') was not found in '@angular/material/menu'

WARNING in ./node_modules/@angular/material/menu/typings/index.ngfactory.js 20:694-701 "export 'ɵd23' (imported as 'i1') was not found in '@angular/material/menu'

WARNING in ./node_modules/@angular/material/menu/typings/index.ngfactory.js 49:191-198 "export 'ɵf23' (imported as 'i1') was not found in '@angular/material/menu'

WARNING in ./src/app/app.component.ngfactory.js 63:1142-1150 "export 'ɵf23' (imported as 'i13') was not found in '@angular/material/menu'

WARNING in ./node_modules/@angular/material/menu/typings/index.ngfactory.js 68:152-159 "export 'ɵf23' (imported as 'i1') was not found in '@angular/material/menu'

WARNING in ./src/app/app.component.ngfactory.js 74:165-173 "export 'ɵf23' (imported as 'i13') was not found in '@angular/material/menu'

WARNING in ./src/app/app.component.ngfactory.js 85:165-173 "export 'ɵf23' (imported as 'i13') was not found in '@angular/material/menu'

WARNING in ./src/app/app.module.ngfactory.js 92:5349-5357 "export 'ɵd23' (imported as 'i40') was not found in '@angular/material/menu'

Anybody any ideas?

Lakston commented 6 years ago

I just tested my link and it works, maybe there was an error on Stackblitz's side when you tried ?

I'll paste it again here just in case.

antontruong commented 6 years ago

Hi,

yes you are right. It worked for. I had to rm node_modules and do a 'npm i' again.

Everything works. Great work thank you.

antontruong commented 6 years ago

Hi,

yes you are right. It worked for me. I had to rm node_modules and do a 'npm i' again.

But now I get this error:

this.openedRow is undefined

Everything works: My bad, I had to remove node_modules and reinstall the deps.

Misiu commented 6 years ago

@Lakston did You noticed that second and third column content is moving when You expand/collapse row? expandbug1

Do You know how this could be fixed?

Lakston commented 6 years ago

I did and no I haven't looked into it, this was just a proof of concept.

badrinath389 commented 6 years ago

How to proceed with expanding all rows at a time with the above example @Misiu @Lakston

kal93 commented 6 years ago

the detail row directive example doesn't seem to work when used with material's native table usage.It adds the template as a cell as opposed to row. https://stackblitz.com/edit/angular-material2-issue-vrnin6?file=app%2Ftable-basic-example.css @shlomiassaf

rdelriods01 commented 5 years ago

@james-schwartzkopf I used your solution in a previous project, but now I want to update that project to Angular 7 and I have difficulties with your library datasource-utils.ts in functions sortRows and paginateRows. I supposed is due to the new rxjs version. Are you still working in your solution?

nareshravlani commented 5 years ago

@Lakston did You noticed that second and third column content is moving when You expand/collapse row? expandbug1

Do You know how this could be fixed?

remove matRipple from mat-row. It will solve the issue. But it will remove animation as well. If you can live without animation, it will work.

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.