components icon indicating copy to clipboard operation
components copied to clipboard

[Table] Add column resizing

Open davidrensh opened this issue 6 years ago • 35 comments

Bug, feature request, or proposal:

Nice to have

What is the expected behavior?

right hand of the sort un-down icon, between column, add an vertical left-right line icon, it will show icon when mouse hover on the column border line

What is the current behavior?

NO

What are the steps to reproduce?

Providing a StackBlitz/Plunker (or similar) is the best way to get the team to see your issue.
Plunker starter (using on @master): https://goo.gl/uDmqyY
StackBlitz starter (using latest npm release): https://goo.gl/wwnhMV

What is the use-case or motivation for changing an existing behavior?

With sort/paging and html5 content edit, we can easily do grid edit now, even without cdk grid. But column resize is pain, we need add css separately.

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

5

Is there anything else we should know?

group by column feature can implement via embedded html table

davidrensh avatar Nov 08 '17 17:11 davidrensh

I'd like to see this too. It's listed as a 'future enhancement' here

chris-jones-pixitmedia avatar Nov 23 '17 08:11 chris-jones-pixitmedia

Is there any timeline about the availability of this feature?

palarnab avatar May 20 '18 05:05 palarnab

@palarnab There's currently no timeline for this feature but we'd happily accept any community contributions to help us get this in. We are currently looking into CDK drag-and-drop support which might be useful for this request

andrewseguin avatar May 21 '18 15:05 andrewseguin

since the drag and drop feature is in beta now, are there any updates on this issue? It seems like this is a pretty core feature for any data table api.

Also, are there any current workarounds or known methods to implement this in Material 6, while the feature is in development?

ayon06 avatar Sep 19 '18 18:09 ayon06

We hope they add this feature and very soon! Unfortunately, not having this feature is a deal-breaker for us so we'll have to use 3rd party tables until the Angular team or contributors add this. Any updates on a timeline?

jrovny avatar Oct 09 '18 04:10 jrovny

@maintainers I implemented the column resizing for a private project. Unluckily I don't have time to propose a PR for this but I can help lending the code if you want a base/some ideas.

IlCallo avatar Oct 23 '18 16:10 IlCallo

I would love to see this feature added to the datatable component.

james-blitzm avatar Nov 02 '18 04:11 james-blitzm

@IlCallo Could you kindly share your implementation, only, of course, if you are not under a NDA?

BugLinker avatar Nov 14 '18 10:11 BugLinker

I can share something but right now I'm a bit on a hurry to finish a project. I'll try to disclose something ASAP but I can't give any assurance about when.

I'll try at least to provide some insight in some days

IlCallo avatar Nov 26 '18 10:11 IlCallo

Here's the code I used. You'll probably need to take into account the added pixels of the resizer when styling resizable cells. If something is not clear, just drop here a question, but I hope comments will help you.

If you have suggestion about how to improve it, I'm interested to hear them :)

Edit: added imports. Note that:

  • CoerceBoolean is a decorator that automatically applies the coerceBooleanProperty method from @angular/cdk to the property
  • BindObservable is this very interesting decorator
  • untilDestroyed is this useful RxJS operator

cell-resizer.component.ts

import {
  ChangeDetectionStrategy,
  Component,
  EventEmitter,
  HostBinding,
  HostListener,
  Input,
  OnDestroy,
  OnInit,
  Output,
} from '@angular/core';
import { CoerceBoolean } from '<private decorator>';
import { BindObservable } from 'bind-observable';
import { untilDestroyed } from 'ngx-take-until-destroy';
import { fromEvent, Observable, Subscription } from 'rxjs';
import { skip } from 'rxjs/operators';

@Component({
  selector: 'cell-resizer',
  template: '',
  styleUrls: ['./cell-resizer.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CellResizerComponent implements OnInit, OnDestroy {
  @Input() private minWidth = 150;

  @HostBinding('class.disabled') @Input() @CoerceBoolean() private disabled = false;

  @Input()
  public get width(): number {
    return this._width;
  }
  // This is needed in case the width given by the server is less than the minimum width
  // At CSS level this is enforced by the 'min-width' property
  public set width(value: number) {
    this._width = Math.max(value, this.minWidth);
  }
  private _width!: number;

  @Output() public resized = new EventEmitter<number>();

  @BindObservable() private isResizing = false;
  private isResizing$!: Observable<boolean>;

  @Output() public resizing = new EventEmitter<boolean>();

  private dragSubscription?: Subscription;

  public ngOnInit(): void {
    this.isResizing$
      .pipe(
        // Skip default value
        skip(1),
        untilDestroyed(this)
      )
      .subscribe(isResizing => {
        this.resizing.emit(isResizing);

        if (isResizing) {
          // We must use arrow function to avoid losing the context,
          //  we cannot pass directly the functions references
          this.dragSubscription = fromEvent<MouseEvent>(window.document, 'mousemove')
            .pipe(untilDestroyed(this))
            .subscribe(event => this.resizeColumn(event));
          this.dragSubscription.add(
            fromEvent(window.document, 'mouseup')
              .pipe(untilDestroyed(this))
              .subscribe(() => this.stopResizig())
          );
        } else {
          // When resize finishes, we emit one last "resized" event for which
          //  the corresponding "isResizing" value will be false.
          // This can be used to detect which is the final resizing event
          //  and ignore the others
          this.resized.emit(this.width);
          if (this.dragSubscription) {
            this.dragSubscription.unsubscribe();
          }
        }
      });
  }

  // We could not put a @HostListener('mousemove', ['$event']) on the resizer itself because
  //  the movement on X axis easily exceed the resizer size,
  //  resulting in the action being interrupted
  // We could not use @HostListener('document:mousemove', ['$event']) either because there is no way
  //  to start&stop it imperatively and mousemove causes global change detection to fire every pixel
  //  if put at document level, blocking the UI when there are a lot of things to render
  // We resolved using Observables from events and subscribing/unsubscribing when resizing
  // TODO check again when HostListener can be start&stopped
  // See https://github.com/angular/angular/issues/7626
  private resizeColumn(event: MouseEvent) {
    const newWidth = this.width + event.movementX;
    if (newWidth >= this.minWidth) {
      this.resized.emit(newWidth);
    }
    // Prevent text selection while resizing
    event.preventDefault();
  }

  // Same problems that mousemove listener have
  private stopResizig() {
    this.isResizing = false;
  }

  // isResizing can be set to true only when the component is not disabled
  @HostListener('mousedown')
  private startResizing() {
    this.isResizing = !this.disabled;
  }

  // Must be present for AOT compilation to work, even if empty
    // Otherwise 'ng build --prod' will optimize away any calls to ngOnDestroy,
  // even if the method is added by the untilDestroyed operator
  public ngOnDestroy() {}
}

cell-resizer.component.scss

:host {
    border-left: 1px solid white;
    border-right: 1px solid white;
    cursor: col-resize;
    display: block;
    height: inherit;
    min-height: inherit;
    min-width: 2px;
    &.disabled {
        cursor: default;
    }
}

Usage Inside the component logic

interface ColumnModel {
  title: string;
  width: number;
}

column: ColumnModel = {
  title: 'Title',
  width: 150
}

isResizing = false;

columnResized(column: ColumnModel, width: number): void {
  column.width = width;
}

Inside the template

<mat-header-cell class="cell--resizable" *matHeaderCellDef [style.flex-basis]="column.width + 'px'">
  <span>{{ column.title }}</span>
  <cell-resizer
    [width]="column.width"
    (resized)="columnResized(column, $event)"
    (resizing)="isResizing = $event"
  ></cell-resizer>
</mat-header-cell>
/*
    [1] The minimum width must be calculated including the padding
    [2] Flex-basis is set to the minimum width by default, shrink and growth are disabled,
          implentation is supposed to bind the calculated width to flex-basis property
*/

.mat-header-cell,
.mat-cell {
  &.cell--resizable {
    box-sizing: border-box; // [1]
    flex: 0 0 150px; // [2]
    min-width: 150px; // [1]
  }
}

IlCallo avatar Nov 26 '18 17:11 IlCallo

Hi @IlCallo, Could you share the imports for the property decorators you use. Also, I defined a custom column data for this issue and in columnResized(column, $event) method I update the widths based on the event. The flex-basis is updating, but the column doesn't resize. And with the scss you gave the cell-resizer within the header is not displayed properly it is not visible. Would you like to share the implementation with the mat-table too so we can see the whole usage.

dimitrov9 avatar Dec 05 '18 08:12 dimitrov9

I edited the previous post to include imports. You are not required to use flex-basis to manage cell width, I did it like this because I'm using table in flex-box mode, but it depends on the use case you have. Share your code and/or some screenshots and I can check if I notice some errors, but yours seem a CSS problem to me

IlCallo avatar Dec 12 '18 11:12 IlCallo

For everyone that also needs to implement resizable columns. I followed the approach from @IlCallo and needed to add the following function in the component that uses the cell-resizer:

  columnResized(element: any, event: any) {
    console.log("Changing width for " + element + " to " + event + " px.");
    var column = <HTMLInputElement>document.getElementById(element);
    column.width = event;
  }

The resizing is now working!

lenngro avatar Feb 06 '19 14:02 lenngro

Yeah, I didn't added columnResized implementation because it's domain-related. I added a minimal example of the logic to the post with all the code.

IlCallo avatar Feb 06 '19 16:02 IlCallo

For everyone that also needs to implement resizable columns. I followed the approach from @IlCallo and needed to add the following function in the component that uses the cell-resizer:

  columnResized(element: any, event: any) {
    console.log("Changing width for " + element + " to " + event + " px.");
    var column = <HTMLInputElement>document.getElementById(element);
    column.width = event;
  }

The resizing is now working!

Hi @IlCallo @lenngro could you share how you handled import { CoerceBoolean } from '<private decorator>'; .. I am unable to follow that line.

shemanifisher avatar Feb 14 '19 16:02 shemanifisher

I couldn't get it to work neither (although I did not try for too long), however not using that decorator does not prevent the resizer from working.

lenngro avatar Feb 14 '19 16:02 lenngro

I couldn't get it to work neither (although I did not try for too long), however not using that decorator does not prevent the resizer from working.

@lenngro

Oh, I also tried without it, it doesnt seem to be problematic. Thank you. although I got resizing working on header only.. below body not moving along.. It will be helpful if you could share your html and columnresize function?

shemanifisher avatar Feb 14 '19 17:02 shemanifisher

CoerceBoolean is a decorator that automatically applies the coerceBooleanProperty method from @angular/cdk to the property

That decorator is just an helper custom decorator I did by myself, of course you cannot follow it 😅

You can just remove it or change it with the more classic coerceBooleanProperty method usage

IlCallo avatar Feb 14 '19 17:02 IlCallo

Hi,

I do column resize with the angular-resizable-element lib. Works with Angular material 7.

Demo and source code of the final render here :

https://stackblitz.com/edit/mat-table-resize

Steps

install https://github.com/mattlewis92/angular-resizable-element

Update table html to be "mwlResizable "

<mat-header-cell *matHeaderCellDef mat-sort-header 
mwlResizable [enableGhostResize]="true" 
(resizeEnd)="onResizeEnd($event, column)"
 [resizeEdges]="{bottom: false, right: true, top: false, left: true}">		
		{{ column | titlecase }}
</mat-header-cell>

Some css

mwlResizable {
		box-sizing: border-box; 
	}

  	mat-cell,
	mat-footer-cell,
	mat-header-cell {
		width: 200px;
		word-break: break-all;
		flex: none; // important : dont be flex
		display: block;
	}

And update each colum size on callback

	onResizeEnd(event: ResizeEvent, columnName): void {
		if (event.edges.right) {
			const cssValue = event.rectangle.width + 'px';
			const columnElts = document.getElementsByClassName('mat-column-' + columnName);
			for (let i = 0; i < columnElts.length; i++) {
				const currentEl = columnElts[i] as HTMLDivElement;
				currentEl.style.width = cssValue;
			}
		}
	}

full source code : https://stackblitz.com/edit/mat-table-resize

stephanebouget avatar Mar 21 '19 15:03 stephanebouget

Did some search and get the material table column resizing working.

Code is here: https://github.com/lujian98/Angular-material-table-Resize

Demo at: https://stackblitz.com/edit/angular-rtfc5v

lujian98 avatar Mar 30 '19 23:03 lujian98

Did some search and get the material table column resizing working.

Code is here: https://github.com/lujian98/Angular-material-table-Resize

Demo at: https://stackblitz.com/edit/angular-rtfc5v

i use this solution it is perfect thanks ;) only one issue - when using with mattable with matsort / sort header when after column resize - sorting is happening... is there any way to prevent sort on resize using this solution ? thanks !

d00lar avatar Sep 27 '19 14:09 d00lar

@d00lar Can you add event.stopPropagation(); to see if this will prevent sort while resize column?

lujian98 avatar Sep 27 '19 15:09 lujian98

yes i checked - still sorting - i think it is triggered berofe on first click but happening after mouse up or something

d00lar avatar Sep 30 '19 05:09 d00lar

i added title of column into span element and assigned mat-sort-header into this span - it is some override to this - now i can resize by clicking anywhere but sort only on clicking directly into center (this span) this way it is not sorting if resizing based on click not on this span with text ... not perfect but works ;P

d00lar avatar Sep 30 '19 05:09 d00lar

Any Updates on a stable solution yet?

RDevR99 avatar Jan 15 '20 23:01 RDevR99

Did some search and get the material table column resizing working. Code is here: https://github.com/lujian98/Angular-material-table-Resize Demo at: https://stackblitz.com/edit/angular-rtfc5v

i use this solution it is perfect thanks ;) only one issue - when using with mattable with matsort / sort header when after column resize - sorting is happening... is there any way to prevent sort on resize using this solution ? thanks !

Hi there, I am wondering do you combine the resizing with the reordering ? they seem to interfere each other

DuoWeng avatar Jul 20 '20 14:07 DuoWeng

i added title of column into span element and assigned mat-sort-header into this span - it is some override to this - now i can resize by clicking anywhere but sort only on clicking directly into center (this span) this way it is not sorting if resizing based on click not on this span with text ... not perfect but works ;P

Could you please elaborate it in detail how you do it, i add the mat-sort-header inside the unfortunately, it does not work and can not be confined in the center of cell

DuoWeng avatar Jul 20 '20 18:07 DuoWeng

Hi All, can we perform both resize and re-ordering operation on Material table columns at a time? resize and reOrdering is working fine for me if I use one at a time, if I apply both operations, then only re-ordering works, Is there any way we can do both the operations on same table at a time?

BhagyashreeCH avatar May 11 '21 10:05 BhagyashreeCH

Sorry for the question, but is there any rough timeline on the experimental code being fixed up and moved to stable? We tried out the experimental code and it has a few bugs, but we need to add column resizing to our tables. If it's coming soonish then we'll hold off, otherwise we'll have to come up with our own solution.

(Also, would it be possible to request that the resizable directive accept a boolean or something to indicate whether the column should be resizable? Not being able to conditionally specify that may be a deal breaker for us, not sure though.)

vaindil avatar Jul 29 '21 20:07 vaindil

Still nothing in 2022?

rene-guerrero avatar Mar 04 '22 13:03 rene-guerrero