Angular

3 Powerful Ways to Reorder Columns in Angular Tables 2025

Tired of static tables? Learn 3 powerful ways to implement column reordering in Angular for 2025, from simple array-slicing to the robust Angular CDK.

E

Elena Petrova

Senior Frontend Engineer specializing in building scalable and interactive UIs with Angular.

7 min read17 views

Let’s be honest: static tables are a relic of the past. In 2025, users don’t just want to see data; they want to interact with it. They expect to sort, filter, and, most importantly, arrange the workspace to fit their unique workflow. One of the most impactful features you can add to any data grid is column reordering.

Giving users the power to drag a "Last Name" column next to a "First Name" column isn't just a nice-to-have feature; it's a fundamental improvement to usability. It reduces friction and makes your application feel more intuitive and personalized.

But how do you actually build this in Angular? You might be surprised by how accessible it is. We're going to explore three powerful, practical methods, from the dead-simple to the incredibly flexible. Whether you're using Angular Material or a custom-built table, there's a solution here for you.

The 3 Methods We'll Cover:

  • Method 1: The Power of Angular CDK Drag and Drop - The go-to, feature-rich solution for most use cases.
  • Method 2: The Manual Array-Driven Approach - A lightweight, dependency-free way for simpler needs.
  • Method 3: Ultimate Flexibility with Dynamic Templates - The advanced pattern for highly configurable enterprise grids.

Method 1: The Power of Angular CDK Drag and Drop

When you want a polished, accessible, and robust drag-and-drop experience, look no further than the official Angular Component Dev Kit (CDK). The DragDropModule is a masterpiece of engineering that handles all the complex pointer events, transformations, and accessibility concerns for you.

This is my default choice for any table built with Angular Material, as it integrates seamlessly.

Step 1: Setting Up the Module

First things first, you need to import the DragDropModule into the Angular module where your table component is declared.

// In your app.module.ts or a shared feature module
import { NgModule } from '@angular/core';
import { DragDropModule } from '@angular/cdk/drag-drop';

@NgModule({
  imports: [
    // ... other modules
    DragDropModule
  ],
  // ...
})
export class YourModule { }

Step 2: Applying the Directives to Your Table

Next, we'll augment our table's header row. We need to tell the CDK which elements form a draggable group and which individual elements are draggable.

  • cdkDropList: This directive goes on the container element for your draggable items. For a table, this is the header row (<tr> or <mat-header-row>).
  • cdkDrag: This directive goes on each individual item you want to be draggable—in our case, each header cell (<th> or <mat-header-cell>).
  • cdkDropListOrientation="horizontal": This is crucial! It tells the CDK to constrain movement to the horizontal axis.

Here’s how it looks on a standard Angular Material table:

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

  <!---
    The main container for column definitions.
    We iterate over 'displayedColumns' which controls the order.
  --->
  <ng-container *ngFor="let column of displayedColumns" [matColumnDef]="column">
    <th mat-header-cell *matHeaderCellDef cdkDrag> {{ column | titlecase }} </th>
    <td mat-cell *matCellDef="let element"> {{ element[column] }} </td>
  </ng-container>

  <!---
    The magic happens here! We wrap the header row.
  --->
  <tr mat-header-row 
      *matHeaderRowDef="displayedColumns"
      cdkDropList 
      cdkDropListOrientation="horizontal"
      (cdkDropListDropped)="onColumnDrop($event)">
  </tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>

</table>

Step 3: Handling the Reorder Event

Advertisement

When the user finishes dragging and drops a column, the (cdkDropListDropped) event fires. This event gives us the previousIndex and currentIndex of the item within the list. Our job is to use this information to reorder our displayedColumns array.

The CDK provides a handy utility function, moveItemInArray, that does exactly this.

import { Component } from '@angular/core';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-my-table',
  // ...
})
export class MyTableComponent {
  // This array defines the initial order and what is shown.
  displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
  dataSource = ELEMENT_DATA; // Your data source

  onColumnDrop(event: CdkDragDrop<string[]>) {
    // The magic function that reorders the array for us.
    moveItemInArray(this.displayedColumns, event.previousIndex, event.currentIndex);
  }
}

And that's it! With just a few lines of code, you have a beautiful, functional column reordering system. The CDK handles the visual feedback (the ghost element, the shifting of other columns) automatically.

Method 2: The Manual Array-Driven Approach

What if you don't want to use the CDK? Maybe you're avoiding extra dependencies, or you need a different kind of UI—like up/down arrows in a settings panel instead of drag-and-drop. This is where a simple, manual approach shines.

The core principle is the same: the visual order of your columns is driven by an array (e.g., displayedColumns). The difference is how we manipulate that array.

Step 1: Defining Your Columns and State

Your component's TypeScript will still hold the single source of truth for column order.

@Component({ ... })
export class MySimpleTableComponent {
  // All possible columns your table *could* show.
  allColumns: string[] = ['id', 'firstName', 'lastName', 'email', 'status'];

  // The columns currently displayed, and in what order.
  displayedColumns: string[] = ['firstName', 'lastName', 'email'];

  // ... your data source
}

Step 2: Building the Reorder Logic

Instead of relying on the CDK, we write our own small function to reorder the displayedColumns array. The Array.prototype.splice method is perfect for this. We'll create a function that can move a column one step to the left or right.

moveColumn(column: string, direction: 'left' | 'right') {
  const currentIndex = this.displayedColumns.indexOf(column);
  
  if (currentIndex === -1) return; // Column not found

  const targetIndex = direction === 'left' ? currentIndex - 1 : currentIndex + 1;

  // Prevent moving beyond array bounds
  if (targetIndex < 0 || targetIndex >= this.displayedColumns.length) {
    return;
  }

  // Create a new array to trigger change detection
  const newColumns = [...this.displayedColumns];
  const [removed] = newColumns.splice(currentIndex, 1);
  newColumns.splice(targetIndex, 0, removed);

  this.displayedColumns = newColumns;
}

Note: We create a new array (`const newColumns = [...this.displayedColumns]`) to ensure Angular's change detection picks up the change, especially if you're using the OnPush strategy.

Step 3: Connecting to the UI

Now, you can create a simple UI, perhaps in a modal or a settings dropdown, that lists the displayed columns and provides buttons to trigger your reordering logic.

<div class="column-settings-panel">
  <h4>Configure Columns</h4>
  <ul>
    <li *ngFor="let col of displayedColumns; let i = index">
      <span>{{ col | titlecase }}</span>
      <div class="controls">
        <button (click)="moveColumn(col, 'left')" [disabled]="i === 0">▲</button>
        <button (click)="moveColumn(col, 'right')" [disabled]="i === displayedColumns.length - 1">▼</button
      </div>
    </li>
  </ul>
</div>

<!-- Your table here, which uses the 'displayedColumns' array -->

This method gives you complete control over the UI and behavior without adding any external libraries. It's clean, simple, and very effective.

Method 3: Ultimate Flexibility with Dynamic Templates

This is the advanced approach, perfect for complex, enterprise-grade applications where users need to not only reorder columns but also toggle their visibility, and where different columns might have completely different cell templates (e.g., a status chip, a date pipe, an action button).

The core idea is to decouple the column *data* (its ID, label, etc.) from its *rendering*. We achieve this with ng-template and ngTemplateOutlet.

Step 1: Structuring Your Column Definitions

Instead of an array of strings, we use an array of objects. Each object describes a column fully.

export interface ColumnDefinition {
  id: string;
  label: string;
  visible: boolean;
}

@Component({ ... })
export class DynamicTableComponent {
  // This is now our single source of truth for all column configuration.
  // Reordering this array reorders the table.
  columnDefs: ColumnDefinition[] = [
    { id: 'name', label: 'Full Name', visible: true },
    { id: 'status', label: 'Status', visible: true },
    { id: 'joinedDate', label: 'Joined Date', visible: false },
    { id: 'actions', label: 'Actions', visible: true },
  ];

  // A getter to easily provide the visible column IDs to the table
  get displayedColumns(): string[] {
    return this.columnDefs.filter(cd => cd.visible).map(cd => cd.id);
  }
}

Step 2: Building the Dynamic Template

The HTML becomes more abstract. We iterate through our `columnDefs` to build the headers. For the cells, we use a `[ngSwitch]` to decide which template to render based on the column's ID.

<!-- Your table component -->
<table mat-table [dataSource]="dataSource">

  <!-- Loop through our rich column definitions -->
  <ng-container *ngFor="let column of columnDefs">
    <ng-container *ngIf="column.visible" [matColumnDef]="column.id">

      <th mat-header-cell *matHeaderCellDef> {{ column.label }} </th>

      <td mat-cell *matCellDef="let element">
        <!-- The magic switch that renders the correct cell content -->
        <ng-container [ngSwitch]="column.id">
          <ng-container *ngSwitchCase="'status'">
            <span class="status-chip status-{{element.status}}">{{ element.status }}</span>
          </ng-container>
          <ng-container *ngSwitchCase="'joinedDate'">
            {{ element.joinedDate | date:'short' }}
          </ng-container>
          <ng-container *ngSwitchCase="'actions'">
            <button mat-icon-button><mat-icon>edit</mat-icon></button>
          </ng-container>
          <ng-container *ngSwitchDefault>
            {{ element[column.id] }}
          </ng-container>
        </ng-container>
      </td>

    </ng-container>
  </ng-container>

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>

</table>

Now, how do you reorder? You simply reorder the columnDefs array! You can use the CDK Drag and Drop logic from Method 1 or the manual array manipulation from Method 2, but apply it to the `columnDefs` array. Toggling visibility is as easy as flipping the `visible` boolean property. This pattern provides incredible power and separation of concerns.

Which Method Is Right for You?

Choosing the right approach depends entirely on your project's needs.

| Method | Best For | Pros | Cons | |---|---|---|---| | 1. CDK Drag and Drop | Most standard use cases, Angular Material tables. | Polished UX, accessible, easy to implement. | Adds a dependency (CDK), opinionated UI. | | 2. Manual Array-Driven | Simple requirements, custom UI controls (buttons, etc.). | Lightweight, no dependencies, full UI control. | Requires writing more boilerplate logic. | | 3. Dynamic Templates | Complex, highly configurable enterprise grids. | Maximum flexibility (reorder, show/hide, custom cells), great separation of concerns. | More complex setup, can be overkill for simple tables. |

Empowering your users with a customizable interface is one of the hallmarks of a modern web application. By implementing column reordering, you're taking a significant step towards a more user-centric design. Start with the CDK method if you're unsure, and scale up in complexity only when your requirements demand it.

Happy coding!

Tags

You May Also Like