About Angular Material Expansion Table

Discussão em 'Angular' iniciado por Jim_corgi, Março 23, 2025.

  1. Jim_corgi

    Jim_corgi Guest

    Github Page: https://windjim.github.io/Angular-Material-Table/expansion-table

    Source code: https://github.com/windjim/Angular-Material-Table/tree/main/src/app/expansion-table

    When I click to expand a row, the functionality works as expected. However, when I sort the data by clicking on the table header, the expanded state becomes inconsistent or incorrect.

    I expect the expanded state to remain unchanged when sorting the data by clicking on the table header.

    code:

    ts

    import {
    Component,
    EventEmitter,
    Injectable,
    Input,
    OnInit,
    Output,
    ViewChild,
    } from '@angular/core';
    import {
    MatPaginator,
    MatPaginatorIntl,
    PageEvent,
    } from '@angular/material/paginator';
    import { Router } from '@angular/router';
    import { MatSort, Sort } from '@angular/material/sort';
    import { MatTableDataSource } from '@angular/material/table';

    // 改變分頁器文字
    @Injectable()
    class CustomMatPaginatorIntl extends MatPaginatorIntl {
    itemsPerPageLabel = '筆數/頁';
    }



    @Component({
    selector: 'app-expansion-table',
    templateUrl: './expansion-table.component.html',
    styleUrls: ['./expansion-table.component.scss'],
    providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }],
    })
    export class ExpansionTableComponent implements OnInit {
    // 外部引入
    @Input() elementData: any[] = []; // 資料
    @Input() jsonFieldName: string[] = []; // JSON對應欄位
    @Input() displayColName: string[] = []; // 顯示用表頭


    @Input() CaseDatas: any[] = []; // 案件總筆數
    @Input() collapseBtn: boolean = true;
    @Input() size: number = 5; // 每頁顯示幾筆
    @Input() headerColumns: string[] = [
    'caseNo',
    "applyTime",
    ]; // 上排欄位定義
    @Input() detailsColumns: string[] = [
    "idNo",
    'applyOption'
    ]; // 下排欄位定義
    @Input() routerLinks: string = ''; //url

    @Output() btnClick = new EventEmitter();

    // 內部變數
    isCollapsed: boolean = false; // 表格收合控制
    paginatedItems: any[] = []; // 分頁顯示資料集合
    searchBtn: string = 'A'; // 查詢按鈕樣式

    maxPage: number = 0; // 最大頁數
    pageInput: number = 1; // 輸入第幾頁
    paginator!: MatPaginator; // 分頁控制器

    @ViewChild('paginator') set paginatorSetter(paginator: MatPaginator) {
    if (paginator) {
    this.paginator = paginator;
    paginator.page.subscribe((event: PageEvent) => this.onPageChange(event));
    }
    }

    @ViewChild(MatSort) sort!: MatSort;

    constructor(private router: Router) {}

    ngOnInit(): void {
    this.setPaginatedItems(0, this.size); //初始化顯示資料筆數
    this.maxPage = Math.ceil(this.CaseDatas.length / this.size); // 計算最大頁數
    }

    // 詳情頁面
    clk(event: MouseEvent, data: any) {
    event.stopPropagation();
    this.btnClick.emit(data);
    }

    // 顯示多少筆數
    setPaginatedItems(startIndex: number, endIndex: number) {
    this.paginatedItems = [
    {
    "applyTime": "2025/03/11",
    "caseNo": "2025031100004",
    "idNo": "A184385581",
    "applyOption": "FO",

    },
    {
    "applyTime": "2025/01/15",
    "caseNo": "2025011400002",
    "idNo": "A127944600",
    "applyOption": "FO",

    },
    {
    "applyTime": "2025/01/14",
    "caseNo": "2025011400001",
    "idNo": "A163479793",
    "applyOption": "F",

    }
    ];
    }

    // 頁數有變化時
    onPageChange(event: PageEvent) {
    this.pageInput = event.pageIndex + 1;
    const startIndex = event.pageIndex * event.pageSize;
    const endIndex = startIndex + event.pageSize;
    this.setPaginatedItems(startIndex, endIndex);
    }

    // 輸入頁數後
    jumpToPage() {
    if (this.pageInput < 1 || this.pageInput > this.maxPage) {
    alert(`請輸入 1 到 ${this.maxPage} 之間的頁碼`);
    return;
    }

    // 將頁碼轉換為索引
    const pageIndex = this.pageInput - 1;
    this.paginator.pageIndex = pageIndex;
    }
    sortData(sort: Sort) {
    const data = this.paginatedItems;

    if (!sort.active || sort.direction === '') {
    this.paginatedItems = data;
    return;
    }

    this.paginatedItems = data.sort((a: any, b: any) => {
    const isAsc = sort.direction === 'asc';
    const key = sort.active as keyof typeof a;

    return this.compare(a[key], b[key], isAsc);
    });

    console.log('this.paginatedItems',data)
    }

    private compare(a: string , b: string, isAsc: boolean): number {
    if (typeof a === 'string' && typeof b === 'string') {
    return (a.toLowerCase() < b.toLowerCase() ? -1 : 1) * (isAsc ? 1 : -1);
    }
    return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
    }
    }


    html:

    <div class="my-3">
    <!-- 左上角的收合按鈕 -->
    <div *ngIf="collapseBtn" class="collapseBtn">
    <button mat-icon-button (click)="isCollapsed = !isCollapsed" class="toggle-btn" style="border: 1px solid grey;">
    {{ isCollapsed ? 'Collapse' : 'Expand' }}
    </button>
    </div>
    <div *ngFor="let item of paginatedItems" class="mb-1">
    <mat-expansion-panel [expanded]="isCollapsed" hideToggle>
    <mat-expansion-panel-header>
    <mat-panel-title>
    <table class="table-container" matSort (matSortChange)="sortData($event)">
    <thead>
    {{isCollapsed ? 'Now is the expansion':'Now is the Collapse'}}
    <tr>
    <th *ngFor="let column of headerColumns;let i = index"
    mat-sort-header="{{item[column]}}"
    [disabled]="column === 'function'">
    {{ column }}
    </th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <td *ngFor="let property of headerColumns">{{ item[property] }}</td>
    </tr>
    </tbody>
    </table>
    </mat-panel-title>
    </mat-expansion-panel-header>

    <div class="d-flex">
    <table class="table-content">
    <tr>
    <th *ngFor="let column of detailsColumns">{{ column }}</th>
    </tr>
    <tr>
    <td *ngFor="let property of detailsColumns">{{item[property]}}</td>
    </tr>
    </table>
    </div>
    </mat-expansion-panel>
    </div>
    </div>

    Continue reading...

Compartilhe esta Página