Components: Support re-ordering tabs via cdk drag-drop

Created on 11 Oct 2018  Â·  20Comments  Â·  Source: angular/components

Not sure if this would be doing some work in cdk/drag-drop that will make it automatically work on the tabs, or if a supplemental directive would be necessary.

Some of my thoughts:

  • Tabs on its own can't have a dependency on cdk/drag-drop
  • Will have to take tab pagination into account, probably autoscrolling
  • Need to decide whether the tabs move as you drag or if it only shows an indicator for where the tab will drop
G P3 cddrag-drop docs

Most helpful comment

Are there are updates wrt documentation for this?

All 20 comments

If we want something that “just works”, we’ll need some kind of abstraction around the mat-tab-group since we don’t have access to the data that’s driving the tabs and the consumer doesn’t have direct access to the tab label itself.

It doesn't necessarily have to work without the user doing anything extra, there just needs to be a supported (documented) way of doing it.

I see. In that case we definitely need some kind of alternate API for the drag&drop that works with generated elements since the tabs work by taking the content, putting it in an ng-template and stamping out the content some place else.

Another thing that came up: dragging tabs between groups

@crisbeto This is a very simple implementation without coupling

https://stackblitz.com/edit/angular-mgrsxx-tfeye2?file=app/tab-group-basic-example.ts

Notes:

  • Don't move the last item until #14158 is merged :)
  • Moving between tab groups is not possible until something like #14153 is possible

Thanks a lot! Indeed our use case does involve moving tabs between tab groups, so will look forward to those two being resolved as well!

@mtaran-google this use case is in most part supported...

Here's a rough POC: https://stackblitz.com/edit/angular-mgrsxx-tzpqjc?file=app%2Ftab-group-basic-example.html

Currently, there are 2 bugs I know of:

  1. Moving the last item in a group will throw it out of bounds. (PR for fix #14158)
  2. Dragging item out of the group and returning it back in the same click will throw it out of bounds (PR for fix ##14153)

The 2nd issue also causes the drop area to be the entire mat-tab-group because it's not possible to pinpoint a specific container area inside the drop container.

Both issues have temp workaround via directives i've wrapped around CdkDrag and CdkDropList...

Other than that all bugs and issues in this POC are solvable with some work on solid wrapper directives that know how to work with the tabs. Maybe some API is required to allow programmatic control over the tabs...

Anyway, I think that each and every CDK components must come with an extensive API that allows complete programmatic control, this is a must.

In angular, your end goal is to work with templates as much as possible, because more template -> less code -> fewer bugs.

But this is true for apps, not for low level components, having extensive API in the low-level components will lead to applications with less code.

I managed it with 7.2 using CDK library:

<mat-tab-group style="position: absolute; background-color: aqua;top: 0;">
  <mat-tab *ngFor="let view of views; let i = index">
    <ng-template mat-tab-label>
      <div [id]="'list-'+i" class="drag-list" cdkDropList cdkDropListOrientation="horizontal" (cdkDropListDropped)="drop($event)" [cdkDropListConnectedTo]="getAllListConnections(index)">
        <div class="drag-box" cdkDrag>{{view}}</div>
      </div>
    </ng-template>
    Content {{view}}
  </mat-tab>
</mat-tab-group>
 getAllListConnections(index){
    var connections = []
    for(var i=0;i<views.length;i++){
      if(i!=index){
        connections.push("list-"+i);
      }
    }
    return connections;
  }



md5-f4236d0d21270d137569f7065edaf61f



drop(event: CdkDragDrop<string[]>) {
    var previousIndex = parseInt(event.previousContainer.id.replace("list-",""));
    var currentIndex = parseInt(event.container.id.replace("list-",""));
    if(previousIndex!=NaN && currentIndex!=NaN && previousIndex!=undefined && currentIndex!=undefined && previousIndex!=currentIndex){
         //Do stuff
        .....
        //END Stuff
        moveItemInArray(this.views, previousIndex, currentIndex);
    }
}

Hope it helps

This works nicely - many thanks. But curiously it seems to kill the isActive flag on the tabs.

is there any progress?

The necessary APIs for this were added in https://github.com/angular/material2/pull/14437, but we haven't gotten around to writing a guide for it yet.

Are there are updates wrt documentation for this?

@MiguelRozalen thanks, your solution works great !
I'm sorry to ask that here but I have an issue that I don't manage to solve : tabs are not reorder dynamically when I'm dragging one tab... (See the expected behaviour here : https://stackblitz.com/edit/angular-kwwarq?file=app%2Fcdk-drag-drop-horizontal-sorting-example.css)
Did you find a way to reorder dynamically the list when you are dragging one tab ?

Are there any updates regarding documentation for drag&drop mat tabs?

Hi @MiguelRozalen can you maybe help me with your implementation Stackoverflow question , do you maybe have idea why this is not working ?

Are there any updates regarding documentation for drag&drop mat tabs?
:D

+1

Any update on this?

Found a working solution:

        <mat-tab-group
            [selectedIndex]="currentStep"
            (selectedIndexChange)="currentStep = $event; updateConnectedTo();"
            style="position: relative; z-index: 0;"
            >
            <mat-tab
                *ngFor="let step of wizard?.components; let stepIndex = index"
                >
                <ng-template mat-tab-label>
                    <div
                        [id]="'list-' + stepIndex"
                        class="steps-drag-list"
                        cdkDropList
                        cdkDropListOrientation="horizontal"
                        (cdkDropListDropped)="dropStep($event)"
                        [cdkDropListConnectedTo]="getAllListConnections(stepIndex)">
                            <div
                                class="step-drag-box"
                                cdkDrag
                                [cdkDragDisabled]="stepIndex === wizard?.components?.length - 1"
                            >
                                {{step.title}}
                                <div
                                    cdkDragHandle
                                    *ngIf="stepIndex < wizard?.components?.length - 1
                                    && this.currentStep === stepIndex"
                                >
                                    <mat-icon class="move-step">open_with</mat-icon>
                                </div>
                            </div>
                    </div>
                </ng-template>
                <div> 
                      {{step}}
                </div>
</...>

```
getAllListConnections(index){
var connections = [];

for(var i=0; i < this.wizard?.components?.length; i++){
  if(i != index){
    connections.push("list-" + i);
  }
}

return connections;

}


dropStep(event: CdkDragDrop const previousIndex = parseInt(event.previousContainer.id.replace("list-",""));
const currentIndex = parseInt(event.container.id.replace("list-",""));

if(currentIndex >= this.wizard?.components?.length - 1)
{
  this.snackBar.open(
    "You cannot put steps after Confirmation Step!",
    "DISMISS",
    {
      duration: 3000,
    }
  );

  return;
}

if ( previousIndex != NaN
  && currentIndex != NaN
  && previousIndex != undefined
  && currentIndex != undefined
  && previousIndex != currentIndex
){
    moveItemInArray(this.wizard?.components, previousIndex, currentIndex);

    let aux = JSON.stringify(this.wizard, this.ignoreNull, ' ');
    this.wizard = JSON.parse(aux); // Needed to avoid Angular Ivy render bug

    this.currentStep = this.wizard?.components?.find((step, i) => currentIndex === i)
      ? currentIndex
      : 0;
}

}
```

Hey! @AlvaroP95 can you please share stackblitz solution as this code is not working for me.
Thanks in advance!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

michaelb-01 picture michaelb-01  Â·  3Comments

savaryt picture savaryt  Â·  3Comments

LoganDupont picture LoganDupont  Â·  3Comments

crutchcorn picture crutchcorn  Â·  3Comments

xtianus79 picture xtianus79  Â·  3Comments