Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support re-ordering tabs via cdk drag-drop #13572

Closed
jelbourn opened this issue Oct 11, 2018 · 26 comments · Fixed by #29517
Closed

Support re-ordering tabs via cdk drag-drop #13572

jelbourn opened this issue Oct 11, 2018 · 26 comments · Fixed by #29517
Assignees
Labels
area: cdk/drag-drop docs This issue is related to documentation G This is is related to a Google internal issue P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent

Comments

@jelbourn
Copy link
Member

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
@jelbourn jelbourn added feature This issue represents a new feature or feature request rather than a bug or bug fix G This is is related to a Google internal issue labels Oct 11, 2018
@crisbeto
Copy link
Member

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.

@jelbourn
Copy link
Member Author

jelbourn commented Oct 11, 2018

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.

@crisbeto
Copy link
Member

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.

@jelbourn
Copy link
Member Author

Another thing that came up: dragging tabs between groups

@shlomiassaf
Copy link
Contributor

@crisbeto This is a very simple implementation without coupling

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

Notes:

@mtaran-google
Copy link

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!

@shlomiassaf
Copy link
Contributor

shlomiassaf commented Nov 15, 2018

@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 fix(drag-drop): use the initial root parent when cleaning up #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 #feat(drag-drop): add the ability to set an alternate drop-list element #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.

@MiguelRozalen
Copy link

MiguelRozalen commented Dec 21, 2018

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;
  }

Then in th event handler you have to manage the order of mat-tabs, like:

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

@kwoxford
Copy link

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

@andreElrico
Copy link

is there any progress?

@crisbeto
Copy link
Member

The necessary APIs for this were added in #14437, but we haven't gotten around to writing a guide for it yet.

@dwightnaylor
Copy link

Are there are updates wrt documentation for this?

@Knu111
Copy link

Knu111 commented Jul 10, 2019

@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 ?

@salesh
Copy link

salesh commented Aug 5, 2019

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

@salesh
Copy link

salesh commented Aug 12, 2019

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

@salesh
Copy link

salesh commented Dec 24, 2019

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

@mmalerba mmalerba added the needs triage This issue needs to be triaged by the team label May 20, 2020
@crisbeto crisbeto added area: cdk/drag-drop docs This issue is related to documentation P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent and removed feature This issue represents a new feature or feature request rather than a bug or bug fix needs triage This issue needs to be triaged by the team labels May 27, 2020
@NashIlli
Copy link

+1

@harshilsharma63
Copy link

Any update on this?

@AlvaroP95
Copy link

AlvaroP95 commented Feb 23, 2021

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<string[]>) {
    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;
    }
}

@harshbairagi
Copy link

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

@agusax28
Copy link

agusax28 commented Apr 7, 2021

I think Ivy don't listen to input changes. Is for that you need to force it.

If you have an easy tabs object, you can use @AlvaroP95 solution.

... //here moveitem
this.tabs.forEach((tab, i) => {
tab.index = i;
tab.template = null; // To avoid stringify circular error
});
let aux = JSON.stringify(this.tabs);
this.tabs = JSON.parse(aux); // ATTENTION: This is a new object, with different reference in memory
this.tabs.forEach((tab: Tab) => (tab.template = this.defaultContent)); // Add the template again

But if you have some map with key reference instead of ids, you need to update it to use the get method from the map.

... //here move item

this.tabs.forEach((oldTab, index) => {
... //like before

  const oldValue = this.myMap.get(oldTab);
  this.myMap.delete(oldTab);
  this.myMap.set(newTab, oldValue);
  this.tabs[index] = newTab;
});

@mykola-novytskyi
Copy link

Hello guys I created a little demo for you if you have still this problem:
https://stackblitz.com/edit/angular-ivy-bvhnt7?file=src%2Fapp%2Fapp.component.ts

@PierreDugue
Copy link

PierreDugue commented Dec 19, 2022

Hello, it seems that auto-scroll is not working when dragging a tab. Is there a way to make it work? See: https://stackblitz.com/edit/angular-wpv2er-djgsig?file=src%2Fapp%2Fcdk-drag-drop-horizontal-sorting-example.ts,src%2Fapp%2Fcdk-drag-drop-horizontal-sorting-example.html

Grab a tab and try to go left or right to make it scroll. Nothing is happening.

@irfanullahjan
Copy link

irfanullahjan commented Jul 26, 2023

A workaround that I used was to switch from the tabs component to Angular Material Button Toggle component. In my case, I had to use a right-click menu on the tabs as well as reorder, and both were giving me headaches with the tabs component.

With the Button Toggle component, both the drag-to-reorder and the right-click menu just seamlessly work without any hacks. Here, I only share selected lines from my solution, I might have missed something but this is so simple, I am sure you guys can make it work in your code just by following the Angular Material docs.

<mat-button-toggle-group
  cdkDropList
  (cdkDropListDropped)="onTabDragDrop($event)"
  cdkDropListOrientation="horizontal"
>
  <mat-button-toggle
    *ngFor="let tab of tabs"
    cdkDrag
  >
    {{ tab.label }}
  </mat-button-toggle>
</mat-button-toggle-group>

In the onTabDragDrop() I just use moveItemInArray util from Angular CDK.

crisbeto added a commit to crisbeto/material2 that referenced this issue Jul 30, 2024
Adds an example that shows how to add drag&drop support to a `mat-tab-group`.

Fixes angular#13572.
@crisbeto crisbeto self-assigned this Jul 30, 2024
crisbeto added a commit that referenced this issue Jul 30, 2024
Adds an example that shows how to add drag&drop support to a `mat-tab-group`.

Fixes #13572.

(cherry picked from commit 67f9a29)
@Kampii
Copy link

Kampii commented Aug 28, 2024

@PierreDugue I have the same exact auto-scroll challenge. Did you find a solution for that?

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 28, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: cdk/drag-drop docs This issue is related to documentation G This is is related to a Google internal issue P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
Projects
None yet
Development

Successfully merging a pull request may close this issue.