Drag and drop an expanded row into another set of expanded rows - Angular Material

Discussão em 'Angular' iniciado por developer8492, Agosto 18, 2025.

  1. I want to have the ability to drag and drop one of the rows that expand in the table to another set of rows that expand in the table. I have added drag and drop properties in the html for the expanded rows (expandedDetail) by using cdkDrag but that isn't working. I am asking for assistance from anyone that is able to help me accomplish this. I am not sure what I'm missing from my code to be able to accomplish this.

    html

    <button (click)="toggleExpandAllElements()">
    {{ !isExpandAll ? 'Expand' : 'Collapse' }} All
    </button>
    <button>Undo Changes</button>
    <button>Logs</button>
    <button>Add/Remove Reps</button>
    <button>Report</button>
    <br />
    <br />

    <!-- {{ displayedColumns | json }} -->
    <table
    mat-table
    [dataSource]="dataSource"
    multiTemplateDataRows
    class="mat-elevation-z8"
    cdkDropListGroup
    >
    <!-- toggle columns menu -->
    <ng-container matColumnDef="{{toggleColDef}}">
    <th mat-header-cell *matHeaderCellDef>
    <button mat-button [matMenuTriggerFor]="menu" class="table-config-menu">
    <mat-icon aria-hidden="false" aria-label="Example home icon"
    >build</mat-icon
    >
    <mat-menu #menu="matMenu">
    <span class="table-config-menu-label">Edit Columns</span>
    <div
    class="table-config-menu-options"
    style="display: flex; flex-direction: column; padding: 8px"
    >
    <mat-checkbox
    *ngFor="let cd of columnDefinitions; let i = index"
    [checked]="isColumnVisible(cd.def)"
    (change)="toggleColumn(cd.def, $event.checked)"
    >
    {{cd.label}}
    </mat-checkbox>
    </div>
    </mat-menu>
    </button>
    </th>
    <td mat-cell *matCellDef="let element"></td>
    </ng-container>

    <ng-container
    matColumnDef="{{column}}"
    *ngFor="let column of columnsToDisplay; let i = index; trackBy column"
    >
    <ng-container>
    <th
    mat-header-cell
    *matHeaderCellDef
    cdkDropList
    cdkDropListLockAxis="x"
    cdkDropListOrientation="horizontal"
    (cdkDropListDropped)="dropListDropped($event, column)"
    cdkDrag
    (cdkDragStarted)="dragStarted($event, i)"
    [cdkDragData]="{name: column, columnIndex: i}"
    >
    {{column}}
    </th>
    <td mat-cell *matCellDef="let element">{{element[column]}}</td>
    </ng-container>
    </ng-container>
    <ng-container matColumnDef="action">
    <th mat-header-cell *matHeaderCellDef>Actions</th>
    <td mat-cell *matCellDef="let element" class="checkbox-spacing">
    <mat-icon (click)="toggleExpandElement(element)"
    >{{isElementExpanded(element) ? 'expand_less' :
    'expand_more'}}</mat-icon
    >
    </td>
    </ng-container>
    <ng-container matColumnDef="select">
    <th mat-header-cell *matHeaderCellDef>
    <mat-checkbox
    (change)="$event ? toggleAllRows() : null"
    [checked]="selection.hasValue() && isAllSelected()"
    [indeterminate]="selection.hasValue() && !isAllSelected()"
    [aria-label]="checkboxLabel()"
    >
    </mat-checkbox>
    </th>
    <td mat-cell *matCellDef="let row">
    <mat-checkbox
    (click)="$event.stopPropagation()"
    (change)="$event ? selection.toggle(row) : null"
    [checked]="selection.isSelected(row)"
    [aria-label]="checkboxLabel(row)"
    >
    </mat-checkbox>
    </td>
    </ng-container>

    <!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->

    <ng-container matColumnDef="expandedDetail">
    <td
    mat-cell
    *matCellDef="let element"
    [attr.colspan]="columnsToDisplayWithExpand.length"
    >
    <div
    class="example-element-detail"
    [@detailExpand]="isElementExpanded(element) ? 'expanded' : 'collapsed'"
    >
    <div></div>
    <!-- <div class="example-element-diagram">
    <div class="example-element-position">{{element.position}}</div>
    <div class="example-element-symbol">{{element.symbol}}</div>
    <div class="example-element-name">{{element.name}}</div>
    <div class="example-element-weight">{{element.weight}}</div>
    </div>
    <div class="example-element-description">
    {{element.description}}
    <span class="example-element-description-attribution">
    -- Wikipedia
    </span>
    </div> -->
    <div class="example-element-name expanded-column-spacing">
    {{element.Name}}
    </div>
    <div class="example-element-weight expanded-column-spacing">
    {{element.weight}}
    </div>
    <div class="example-element-weight expanded-column-spacing">
    {{element.weight}}
    </div>
    </div>
    </td>
    </ng-container>

    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
    <tr
    mat-row
    *matRowDef="let element; columns: displayedColumns;"
    class="example-element-row"
    [class.example-expanded-row]="isElementExpanded(element)"
    ></tr>
    <tr
    mat-row
    *matRowDef="let row; columns: ['expandedDetail']"
    (click)="toggle(row)"
    cdkDrag
    cdkDragLockAxis="y"
    [cdkDragData]="row" ></tr>
    <tr
    mat-row
    *matRowDef="let row; columns: ['expandedDetail']"
    class="example-detail-row"
    ></tr>
    <tr
    mat-row
    *matRowDef="let row; columns: ['expandedDetail']"
    class="example-detail-row"
    ></tr>
    <tr
    mat-row
    *matRowDef="let row; columns: ['expandedDetail']"
    class="example-detail-row"
    ></tr>
    </tr>
    <tr mat-row *matRowDef="let row; columns: getDisplayedColumns()"></tr>
    </table>


    ts

    import { SelectionModel } from '@angular/cdk/collections';
    import { MatTableDataSource } from '@angular/material/table';
    import { Component } from '@angular/core';

    import {
    CdkDragStart,
    CdkDropList,
    CdkDragDrop,
    moveItemInArray,
    } from '@angular/cdk/drag-drop';
    import {
    animate,
    state,
    style,
    transition,
    trigger,
    } from '@angular/animations';

    /**
    * @title Table with expandable rows
    */
    @Component({
    selector: 'table-expandable-rows-example',
    styleUrls: ['table-expandable-rows-example.scss'],
    templateUrl: 'table-expandable-rows-example.html',
    animations: [
    trigger('detailExpand', [
    state('collapsed', style({ height: '0px', minHeight: '0' })),
    state('expanded', style({ height: '*' })),
    transition(
    'expanded <=> collapsed',
    animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')
    ),
    ]),
    ],
    })
    export class TableExpandableRowsExample {
    columnsToDisplay: string[] = [
    'Name',
    'weight',
    'symbol',
    'position',
    ];

    columns: any[] = ['Name', 'weight', 'symbol', 'position'];
    columnsToDisplayWithExpand = ['select', 'action', ...this.columnsToDisplay];

    dataSource = new MatTableDataSource<PeriodicElement>(ELEMENT_DATA);
    expandedElement: PeriodicElement | null;
    selection = new SelectionModel<PeriodicElement>(true, []);

    expandedElements: PeriodicElement[] | null = [];

    // drop(event: CdkDragDrop<string[]>) {
    // moveItemInArray(
    // this.columnsToReorder,
    // event.previousIndex,
    // event.currentIndex
    // );
    // }
    previousIndex: number;

    setDisplayedColumns() {
    this.displayedColumns = [
    'select',
    'action',
    ...this.columns,
    this.toggleColDef,
    ];
    }

    dragStarted(event: CdkDragStart, index: number) {
    const prevIndex = this.columns.indexOf(event.source.data.name);
    this.previousIndex = prevIndex;
    }

    dropListDropped(
    event: CdkDragDrop<any>,
    targetColumn: string
    ) {
    if (event) {
    const dropIndex = this.columns.indexOf(targetColumn);

    console.log(
    `${event.item.data.name} Move from ${this.previousIndex} to ${dropIndex}`
    );

    moveItemInArray(this.columns, this.previousIndex, dropIndex);

    this.setDisplayedColumns();
    }
    }
    /** Whether the number of selected elements matches the total number of rows. */
    isAllSelected() {
    const numSelected = this.selection.selected.length;
    const numRows = this.dataSource.data.length;
    return numSelected === numRows;
    }

    /** Selects all rows if they are not all selected; otherwise clear selection. */
    toggleAllRows() {
    if (this.isAllSelected()) {
    this.selection.clear();
    return;
    }

    this.selection.select(...this.dataSource.data);
    }

    /** The label for the checkbox on the passed row */
    checkboxLabel(row?: PeriodicElement): string {
    if (!row) {
    return `${this.isAllSelected() ? 'deselect' : 'select'} all`;
    }
    return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${
    row.position + 1
    }`;
    }
    columnDefinitions = [
    { def: 'Name', label: 'Name', visible: true },
    {
    def: 'weight',
    label: 'Weight',
    visible: true,
    },
    {
    def: 'symbol',
    label: 'Symbol',
    visible: true,
    },
    {
    def: 'position',
    label: 'Position',
    visible: true,
    },
    ];

    toggleColDef = 'toggleCol';

    displayedColumns: any = [
    ...this.columnsToDisplayWithExpand,
    this.toggleColDef,
    ];

    // all columns for tracking order
    allCols = [...this.displayedColumns];

    // use Set to track checked columns
    checkedColumns = new Set(this.displayedColumns);

    toggleColumn(col: string, checked: boolean) {
    if (checked) {
    // add to checked columns if checked
    this.checkedColumns.add(col);
    } else {
    // remove
    this.checkedColumns.delete(col);
    }

    // recreate columns in the same order, add toggle manually
    this.displayedColumns = this.allCols.filter((el) => {
    if (el === this.toggleColDef) {
    return true;
    } else {
    return this.checkedColumns.has(el);
    }
    });
    }

    isColumnVisible(col: string): boolean {
    return this.checkedColumns.has(col);
    }

    getDisplayedColumns(): string[] {
    return this.columnDefinitions
    .filter((cd) => cd.visible)
    .map((cd) => cd.def);
    }

    get isExpandAll() {
    return this.expandedElements.length == this.dataSource.data.length;
    }

    toggleExpandAllElements() {
    if (!this.isExpandAll) {
    this.expandedElements = [...this.dataSource.data];
    } else {
    this.expandedElements = [];
    }
    }

    toggleExpandElement(element: PeriodicElement) {
    const index = this.expandedElements.indexOf(element);
    if (index > -1) {
    this.expandedElements.splice(index, 1);
    } else {
    this.expandedElements.push(element);
    }
    }

    isElementExpanded(element) {
    return this.expandedElements.indexOf(element) > -1;
    }
    }

    export interface PeriodicElement {
    Name: string;
    position: number;
    weight: number;
    symbol: string;
    description: string;
    }

    const ELEMENT_DATA: PeriodicElement[] = [
    {
    position: 1,
    Name: 'Hydrogen',
    weight: 1.0079,
    symbol: 'H',
    description: `Hydrogen is a chemical element with symbol H and atomic number 1. With a standard
    atomic weight of 1.008, hydrogen is the lightest element on the periodic table.`,
    },
    {
    position: 2,
    Name: 'Helium',
    weight: 4.0026,
    symbol: 'He',
    description: `Helium is a chemical element with symbol He and atomic number 2. It is a
    colorless, odorless, tasteless, non-toxic, inert, monatomic gas, the first in the noble gas
    group in the periodic table. Its boiling point is the lowest among all the elements.`,
    },
    ];


    The code above in the stackblitz link below along with other files like the css file. I would appreciate any assistance or guidance anyone has to get this to work. If there are other alternatives to use besides drag and drop that would be helpful also.

    Stackblitz Demo

    Continue reading...

Compartilhe esta Página