All Articles

Angular CDK Drag/Drop List inside a table (not Material Table) - Handle rows distorting width

TL;DR - When using Angular CDK Drag/Drop in a normal table using table, tr and td, you can manually set the column width to prevent rows distorting width. If you are using mat-table together with mat-row and mat-cell, you don’t have to worry about the rows distorting width issue.

Working Example

Problem

In Zyllem, many places need to configure the order of each item, and this order will become very important when the runtime kicked in. In the beginning, our backend used MVC and Razor to build form, so usually, we will add one textbox to input the order number. Imagine you have a list of 10 items and you did the numbering. But then one day you need to change the number 5 to be the first one in the list so that you will have to manually update all the other 9 items as well. And it is also error prone because there is no validation, so there could be two items with the same number.

Recently, we migrated some of the UI to client-side. We removed the order textbox, instead, we let the user drag and drop to change the order.

To start using CDK Drag and drop, very simple. Start by importing DragDropModule into the NgModule where you want to use drag-and-drop features. You can now add the cdkDrag directive to elements to make them draggable. When the element is outside of a cdkDropList element, draggable elements can be freely moved around the page. You can add cdkDropList elements to constrain where elements may be dropped.

If you are using mat-table together with mat-row and mat-cell, you don’t have to worry about the rows distorting width. It is supported out of the box for you. See this working example of mat-table on https://stackoverflow.com/a/58727454/3375906

1. Configure drag and drop inside a table

Usually, we will have to use it in a table view. See the code below to enable drag and drop inside a table. It is to display a list of users to the screen.

<table class="table">
  <thead>
    <tr>
      <th scope="col">#</th>
      <th scope="col">First</th>
      <th scope="col">Last</th>
      <th scope="col">Email</th>
      <th scope="col">Address</th>
    </tr>
  </thead>
  <tbody cdkDropList (cdkDropListDropped)="onDrop($event)">
    <tr *ngFor="let user of users" cdkDrag cdkDragLockAxis="y">
      <th>
        <div class="drag-handle">
          <ng-container [ngTemplateOutlet]="dragHandleTmpl"> </ng-container>
          {{ user.order }}
        </div>
      </th>
      <td>{{ user.first }}</td>
      <td>{{ user.last }}</td>
      <td>{{ user.email }}</td>
      <td>{{ user.address }}</td>
    </tr>
  </tbody>
</table>

By simple adding cdkDropList into the tbody, and cdkDrag to each tr. I simply mean I wanted each row to be able to move inside the body. cdkDragLockAxis="y" to force the row to move upward and downward only, not freely. The result is displayed below.

Angular CDK Drag/Drop List inside table (not Material Table) - Handle rows distorting width

2. Add animation

Now each row can be move, but it hasn’t looked great yet. We need to add a few more lines of CSS to add animation to the drag and drop event. Refer to this documentation for the full list of CSS class that you can update.

By only added these two lines, it looks much better now.

/* Animate items as they're being sorted. */
.cdk-drop-list-dragging .cdk-drag {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
  padding: 4px;
}

Angular CDK Drag/Drop List inside table (not Material Table) - Handle rows distorting width

3. Fix rows distorting width

But do you notice one thing? When you start dragging the row, The preview row has all of the columns stick together. I looked at the source and saw that Angular added cdk-drag-preview to the top level of body so that the style of the table was losing.

Angular CDK Drag/Drop List inside table (not Material Table) - Handle rows distorting width

I heard the cdk drag and drop work well with Material table. Because in material table, you can use the flexbox implementation. For a normal table, I came up with a solution to manually set the width of each column by CSS, it didn’t look the same as each row in the table, but the result is acceptable until Angular team provided us a proper solution for supporting table. Table column is a complicated topic, you can see all the rules of how the table column width is calculated in the W3 Table specs.

.col-xs {
  width: 2%;
}

.col-sm {
  width: 10%;
}

.col-md {
  width: 20%;
}
<tbody cdkDropList (cdkDropListDropped)="onDrop($event)">
  <tr *ngFor="let user of users" cdkDrag cdkDragLockAxis="y">
    <th class="col-xs">
      <div class="drag-handle">
        <ng-container [ngTemplateOutlet]="dragHandleTmpl"> </ng-container>
        {{ user.order }}
      </div>
    </th>
    <td class="col-md">{{ user.first }}</td>
    <td class="col-md">{{ user.last }}</td>
    <td class="col-md">{{ user.email }}</td>
    <td class="col-md">{{ user.address }}</td>
  </tr>
</tbody>

And this is the result…

Angular CDK Drag/Drop List inside table (not Material Table) - Handle rows distorting width

Published 3 Apr 2020

Recent Posts

Angular - Correct singular/plural form of a noun using custom pipe or NgPlural

Pluralization is a problem in its sphere. We need to always correctly define grammar in our apps based on the singular/plural value

Error handling in JavaScript. Synchronous vs asynchronous code

Error handling in JavaScript has been always straightforward, at least for synchronous code


Follow @tuantrungvo on Twitter for more!