angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.32k stars 6.73k forks source link

CDK Drag Drop issues with nested lists #16671

Open shesse opened 5 years ago

shesse commented 5 years ago

Reproduction

https://stackblitz.com/edit/angular-drag-and-drop-child-groups-fxzjja?file=app%2Fcdk-drag-drop-connected-sorting-group-example.html

Steps to reproduce:

  1. Try to drag element "Brush teeth" to before "Drive to office" or before "Walk dog"
  2. this will not be possible

Expected Behavior

Drag / Drop to all lists contained in cdkDropListConnectedTo

Actual Behavior

Items from inner Lists may only be dropped to outer lists, not to sibling or their own lists

Environment

Additional Info

When using CDK drag / drop (8.0.0 up to and including 8.1.2) with nested lists, two issues arise that prevent an easy use of the feature:

a) dragging does not work from inner list to inner list

b) dragging does not work in the drop item's source list

Explanation and proposed solution.

For above case a) mentioning it in the documentation may be sufficient - it can be influenced from the outside. However, a more intelligent solution (e.g. identifying the toplevel sublist) would be nice.

For case b), filtering out the source list in src/cdk/drag-drop/directives/drop-list.ts seems to cause the issue: ref.connectedTo(siblings.filter(drop => drop && drop !== this).map(list => list._dropListRef)) My hack replaces this by an unfiltered list - in quite an ugly manner.

denkerny commented 4 years ago

it doesnt work well, cant get needed element index in nested array.

james-logan commented 4 years ago

+1

crisbeto commented 4 years ago

Setting as a P2 based on the number of duplicate issue we get about this.

crisbeto commented 4 years ago

From @matthewerwin in #19085:

Reproduction

Use StackBlitz to reproduce your issue:

https://stackblitz.com/edit/angular-rf6gg6

Steps to reproduce:

  1. Look at the stack blitz.
  2. Markup has an effect on ability to even start a drag without any console or compile indicator that cdkDrag and cdkDropList cannot be on the same drag element (they could be in v8 --v9 allows them on the same container for the "drop element")
  3. Exit/Enter aren't reliably detected based on the client-rect

Expected Behavior

What behavior were you expecting to see?

  • Drop zone container should always be detected as soon as the mouse goes over the element.
  • If use of cdkDrag/cdkDropList combo only works in certain situations (e.g. codebase looks for this._container on a dragref in v9...which appears to only get set if cdkDropList is on a parent element). That should either be documented and also the compiler should write an error to the console...or the code should be adjusted to set this._container to the element itself to handle the directive combo on the same element.

Actual Behavior

What behavior did you actually see?

  • No errors to console or at compile time with directives appended in any fashion.
  • v9 does not detect a drop-zone accurately at all for entry or exit. Shaking the mouse a bunch over drop zones or redragging repeatedly will randomly then detect it correctly...though continuing to drag around shows the same inability to detect the drop-zones.
f1nality commented 4 years ago

+1

admosity commented 3 years ago

So it seems like there is some internal priority here. I've gotten pretty decent results with sorting the lists by dom depth as a workaround:

    this.connectedDroplists$ = this._connectedDroplists$.pipe(
      debounceTime(16),
      tap((connectedDropLists) => {
        // sort the lists by depth to prefer deeper lists
        connectedDropLists.sort((a, b) => {
          return (
            this._getDepth(b.element.nativeElement) -
            this._getDepth(a.element.nativeElement)
          );
        });
      }),
      shareReplay(1)
    ) as BehaviorSubject<CdkDropList[]>;

It seems that no matter what I do, I can't seem to get it to re-order within the source droplist where I grabbed an item from.

Sorting the droplists by element depth and using the enforce hack that @shesse has provided, I have gotten an excellent result even with completely overlapping nested drop lists (looks like I had a gap that allowed for me to add to the parent). Thank you @shesse!

I suppose the reversing makes sense because the deeper the list is the later it would be registered (resulting it to be at the end).

Oct-21-2020 14-21-08

soren-petersen commented 3 years ago

Hi everyone,

I ran into this issue a few days ago and decided to make an attempt at fixing it :-) I have tried to summarise my "analysis" of the issue and a proposal for how to solve it below. I apologise for - to some extent - repeating what has already been said above. I have also attached "Monkey Patch" that seems to fix the issue for Angular Material v11.0.3. I will try to transform it into a pull request shortly.

The main issue seems to be with the implementation of _getSiblingContainerFromPosition in DropListRef which is used by _updateActiveDropContainer in DragRef to identify whether a draggable item is being moved over a new (and connected) drop container and, if it is, which drop container the item is being moved over.

Based on the code and comments in _updateActiveDropContainer (which is the only place where _getSiblingContainerFromPosition is called), _getSiblingContainerFromPosition is expected to return the DropListRef for the drop container that the item is being moved over, provided that it is not the drop container that it currently belongs to. The method is expected to return 'undefined' if the item is being moved over the current drop container or if it is being moved over something that is not a drop container.

The current implementation of _getSiblingContainerFromPosition does this by returning the first connected drop container ("sibling") in the list of siblings that has a client rect that contains the current position of the item being dragged and for which enterPredicate returns true (I know this is somewhat simplified, but the still argument holds). This approach works perfectly well as long the client rects of the drop containers are not overlapping.

However, when drop containers are nested (and therefore overlapping), this causes two problems:

  1. If an outer drop container is listed before an inner drop container in the list of siblings, _getSiblingContainerFromPosition will return the outer drop container when the item is moved over the inner container. Hence, the list of siblings must be ordered to list inner drop containers before outer drop containers (as mentioned above)
  2. As the list of siblings does not include the current drop container ("this"), _getSiblingContainerFromPosition will always return the outer drop container when an item of the inner drop container is dragged over the inner container. As a result, it is not possible to re-order items in an inner drop container.

The first issue can be addressed by ordering the items of 'cdkDropListConnectedTo' (as mentioned above). The current drop container can, however, not be added to siblings due to the code in _setupInputSyncSubscription of DropList that actively removes 'this' from the list (as pointed out by @shesse). The hack described by @shesse above addresses this issue by forcing the 'this' entry back into the siblings list. This approach does, however, bring the risk of breaking other code as the siblings list is clearly not intended to include 'this'.

My proposal would be to fix the issue by rewriting _getSiblingContainerFromPosition such that it correctly handles nested drop containers without any assumptions on the order of the siblings list. This could be done in the following way:

I have created a "Monkey Patch" that seems to work with Angular Material version 11.0.3 (attached). It should be straight forward to transform this into a pull request. I will gladly do so (but never having done a pull request before, I'll have to read up on how to do it)

nested-drag-drop-patch.ts.zip

I suspect thet the code breaks more than one Angular Material coding guideline. It is also a quick write-up so I would not be surprised if there are some issues that need to be resolved. A proper code review might be in order ... :-)

FritzHerbers commented 3 years ago

3 years ago I tried to use drag-drop, but got stuck, some issues were closed due to inactivity. I might have a need in drag-drop in half a year, maybe I will immediately look for another implementation. So I am far off, what is currently implemented or which issues are open and solved, and if this is the correct place for:

soren-petersen commented 3 years ago

@FritzHerbers

I propose you start by browsing through the excellent documentation for CDK Drag and Drop. I think you will find that the drag-drop feature has improved significantly over the last couple of years. You will likely also find the answer to your callback question there. Have a look at cdkDropListEnterPredicate and cdkDropListSortPredicate.

The issue tracker and this particular issue is, however, not the right place for a general discussion about the state of cdk/drag-drop nor your specific question of support for a programatic callback. If you have further questions, community support requests can be submitted at StackOverflow or in the Angular Material gitter channel.

Please refer to the official instruction for support questions for more details.

Akhil-Myadarapu commented 3 years ago

hi @soren-petersen. can you attach stackblitz link for the same. It will be very helpful. thanks

soren-petersen commented 3 years ago

Hi @Akhil-Myadarapu,

I'm sorry, but I really do not have the time to put together a presentable demo right now. Feel free to put one up if you have more time on your hands than I.

In order to use the "monkey patch" that I attached in a previous post, you only need to import the "installPatch" function from the module:

import { installPatch } from './nested-drag-drop-patch'

and then call it at the root level of your .ts file:

installPatch();

Apart from that, you need to set up a nested tree with cdkDropList and cdkDrop in your template. Remember to use cdkDropListConnectedTo to connect all the drop lists. Using cdkDropListGroup will not work for a nested setup.

You might find that the patch generates some warnings when running lint. These have been fixed in the pull request. Have a look at that if you want the latest version of the fix.

jneuhaus20 commented 3 years ago

@Akhil-Myadarapu, I added the monkey patch to @shesse's original StackBlitz, and then replaced its content with the method bodies from the PR (commit d3690c3):

It works pretty good!

@soren-petersen During dragging, there's some trouble transitioning to a nested list (coming from the top, it doesn't change until the 2nd or 3rd item down; coming from the bottom it wants to drop before the nested list for some pixels, than switches to into.) Not sure if I should make the comments in the PR itself as well?

aavelyn commented 3 years ago

@soren-petersen I've used the zip.file to patch the cdk and it does solve the original issue here.

https://stackblitz.com/edit/angular-10-clarity-4-dragdrop-nested-with-fix

However dragging an item from a child to it's parent seems to have very narrow window. In my example the preview element has a height of 20px. Without the patch, the window works as expected (with the bug of this issue of course). With the patch the window seems to be only like a pixel or so.

stefan0s commented 3 years ago

So it seems like there is some internal priority here. I've gotten pretty decent results with sorting the lists by dom depth as a workaround:

    this.connectedDroplists$ = this._connectedDroplists$.pipe(
      debounceTime(16),
      tap((connectedDropLists) => {
        // sort the lists by depth to prefer deeper lists
        connectedDropLists.sort((a, b) => {
          return (
            this._getDepth(b.element.nativeElement) -
            this._getDepth(a.element.nativeElement)
          );
        });
      }),
      shareReplay(1)
    ) as BehaviorSubject<CdkDropList[]>;

~It seems that no matter what I do, I can't seem to get it to re-order within the source droplist where I grabbed an item from.~

Sorting the droplists by element depth and using the enforce hack that @shesse has provided, ~I have gotten an excellent result even with completely overlapping nested drop lists~ (looks like I had a gap that allowed for me to add to the parent). Thank you @shesse!

I suppose the reversing makes sense because the deeper the list is the later it would be registered (resulting it to be at the end).

Oct-21-2020 14-21-08

Hello admosiry,

Is there any demo for the image provided? I am looking to do something similar. I am using flex to define double/single columns and I am having issues while dragging. Elements flipping while dragging

pdura commented 3 years ago

thanks @soren-petersen ! Your patch just saved my day !

hannah23280 commented 2 years ago

The unability to support sorting in nested lists (without any monkey patch) , is there any plan to fixed it for the upcoming Nov release angular 13?

EmaGht commented 2 years ago

It's getting tiring to manually tweak literally every frontend library there is. If you guys implement this one you will probably be the first instance of a library i didn't have to hammer down to my needs

Go down in history and implement this! Pretty please :cry:

mironante commented 2 years ago

Hi everyone,

I ran into this issue a few days ago and decided to make an attempt at fixing it :-) I have tried to summarise my "analysis" of the issue and a proposal for how to solve it below. I apologise for - to some extent - repeating what has already been said above. I have also attached "Monkey Patch" that seems to fix the issue for Angular Material v11.0.3. I will try to transform it into a pull request shortly.

The main issue seems to be with the implementation of _getSiblingContainerFromPosition in DropListRef which is used by _updateActiveDropContainer in DragRef to identify whether a draggable item is being moved over a new (and connected) drop container and, if it is, which drop container the item is being moved over.

Based on the code and comments in _updateActiveDropContainer (which is the only place where _getSiblingContainerFromPosition is called), _getSiblingContainerFromPosition is expected to return the DropListRef for the drop container that the item is being moved over, provided that it is not the drop container that it currently belongs to. The method is expected to return 'undefined' if the item is being moved over the current drop container or if it is being moved over something that is not a drop container.

The current implementation of _getSiblingContainerFromPosition does this by returning the first connected drop container ("sibling") in the list of siblings that has a client rect that contains the current position of the item being dragged and for which enterPredicate returns true (I know this is somewhat simplified, but the still argument holds). This approach works perfectly well as long the client rects of the drop containers are not overlapping.

However, when drop containers are nested (and therefore overlapping), this causes two problems:

  1. If an outer drop container is listed before an inner drop container in the list of siblings, _getSiblingContainerFromPosition will return the outer drop container when the item is moved over the inner container. Hence, the list of siblings must be ordered to list inner drop containers before outer drop containers (as mentioned above)
  2. As the list of siblings does not include the current drop container ("this"), _getSiblingContainerFromPosition will always return the outer drop container when an item of the inner drop container is dragged over the inner container. As a result, it is not possible to re-order items in an inner drop container.

The first issue can be addressed by ordering the items of 'cdkDropListConnectedTo' (as mentioned above). The current drop container can, however, not be added to siblings due to the code in _setupInputSyncSubscription of DropList that actively removes 'this' from the list (as pointed out by @shesse). The hack described by @shesse above addresses this issue by forcing the 'this' entry back into the siblings list. This approach does, however, bring the risk of breaking other code as the siblings list is clearly not intended to include 'this'.

My proposal would be to fix the issue by rewriting _getSiblingContainerFromPosition such that it correctly handles nested drop containers without any assumptions on the order of the siblings list. This could be done in the following way:

  • Create a list containing all siblings and 'this'
  • Filter the list so that it only contains those drop containers that has a client rect that contains the current item position
  • Sort the remaining items based on the DOM hierarchy so that outer drop containers appear before the inner drop containers that they contain
  • Extract the last item of the sorted list
  • Check whether the extracted item is indeed a match using _canReceive
  • if _canReceive returns true, return the extracted item otherwise return 'undefined'

I have created a "Monkey Patch" that seems to work with Angular Material version 11.0.3 (attached). It should be straight forward to transform this into a pull request. I will gladly do so (but never having done a pull request before, I'll have to read up on how to do it)

nested-drag-drop-patch.ts.zip

I suspect thet the code breaks more than one Angular Material coding guideline. It is also a quick write-up so I would not be surprised if there are some issues that need to be resolved. A proper code review might be in order ... :-)

Really helpful! Thank you!

willehpeh commented 2 years ago

Hi everyone,

I ran into this issue a few days ago and decided to make an attempt at fixing it :-) I have tried to summarise my "analysis" of the issue and a proposal for how to solve it below. I apologise for - to some extent - repeating what has already been said above. I have also attached "Monkey Patch" that seems to fix the issue for Angular Material v11.0.3. I will try to transform it into a pull request shortly.

The main issue seems to be with the implementation of _getSiblingContainerFromPosition in DropListRef which is used by _updateActiveDropContainer in DragRef to identify whether a draggable item is being moved over a new (and connected) drop container and, if it is, which drop container the item is being moved over.

Based on the code and comments in _updateActiveDropContainer (which is the only place where _getSiblingContainerFromPosition is called), _getSiblingContainerFromPosition is expected to return the DropListRef for the drop container that the item is being moved over, provided that it is not the drop container that it currently belongs to. The method is expected to return 'undefined' if the item is being moved over the current drop container or if it is being moved over something that is not a drop container.

The current implementation of _getSiblingContainerFromPosition does this by returning the first connected drop container ("sibling") in the list of siblings that has a client rect that contains the current position of the item being dragged and for which enterPredicate returns true (I know this is somewhat simplified, but the still argument holds). This approach works perfectly well as long the client rects of the drop containers are not overlapping.

However, when drop containers are nested (and therefore overlapping), this causes two problems:

  1. If an outer drop container is listed before an inner drop container in the list of siblings, _getSiblingContainerFromPosition will return the outer drop container when the item is moved over the inner container. Hence, the list of siblings must be ordered to list inner drop containers before outer drop containers (as mentioned above)
  2. As the list of siblings does not include the current drop container ("this"), _getSiblingContainerFromPosition will always return the outer drop container when an item of the inner drop container is dragged over the inner container. As a result, it is not possible to re-order items in an inner drop container.

The first issue can be addressed by ordering the items of 'cdkDropListConnectedTo' (as mentioned above). The current drop container can, however, not be added to siblings due to the code in _setupInputSyncSubscription of DropList that actively removes 'this' from the list (as pointed out by @shesse). The hack described by @shesse above addresses this issue by forcing the 'this' entry back into the siblings list. This approach does, however, bring the risk of breaking other code as the siblings list is clearly not intended to include 'this'.

My proposal would be to fix the issue by rewriting _getSiblingContainerFromPosition such that it correctly handles nested drop containers without any assumptions on the order of the siblings list. This could be done in the following way:

  • Create a list containing all siblings and 'this'
  • Filter the list so that it only contains those drop containers that has a client rect that contains the current item position
  • Sort the remaining items based on the DOM hierarchy so that outer drop containers appear before the inner drop containers that they contain
  • Extract the last item of the sorted list
  • Check whether the extracted item is indeed a match using _canReceive
  • if _canReceive returns true, return the extracted item otherwise return 'undefined'

I have created a "Monkey Patch" that seems to work with Angular Material version 11.0.3 (attached). It should be straight forward to transform this into a pull request. I will gladly do so (but never having done a pull request before, I'll have to read up on how to do it)

nested-drag-drop-patch.ts.zip

I suspect thet the code breaks more than one Angular Material coding guideline. It is also a quick write-up so I would not be surprised if there are some issues that need to be resolved. A proper code review might be in order ... :-)

Thank you so much for this patch, you're an absolute legend!

wawyed commented 2 years ago

Could we have an update from the angular contributors POV? What's the status of #21526 ? It would be nice to get this moved forward.

ZycaR commented 2 years ago

@crisbeto - drop list should definitely have the option to count itself when searching for containers from position (and disable this filtering). This will give us an opportunity to specify order in case of overlapping drop lists.

also, I prepared a clone of @shesse StackBlitz with a different fix. Fix that overwrites _getSiblingContainerFromPosition to search over custom-defined list of containers/siblings. Like this, you should be able to keep the order you specified and mitigate side effects.

https://stackblitz.com/edit/angular-drag-and-drop-child-groups-9bmj6t?file=app%2Fcdk-drag-drop-connected-sorting-group-example.ts

JaxonWright commented 2 years ago

Really would be nice if the "monkey patch" PR could be looked at again by maintainers and merged....

LinboLen commented 2 years ago

if cdk implement flex layout would be great. like this.

drag-and-drop

Qrauzer commented 2 years ago

@crisbeto - drop list should definitely have the option to count itself when searching for containers from position (and disable this filtering). This will give us an opportunity to specify order in case of overlapping drop lists.

also, I prepared a clone of @shesse StackBlitz with a different fix. Fix that overwrites _getSiblingContainerFromPosition to search over custom-defined list of containers/siblings. Like this, you should be able to keep the order you specified and mitigate side effects.

https://stackblitz.com/edit/angular-drag-and-drop-child-groups-9bmj6t?file=app%2Fcdk-drag-drop-connected-sorting-group-example.ts

Thanks A LOT for the given fix! I'm so happy now

MichaelChappell0117 commented 2 years ago

@crisbeto - drop list should definitely have the option to count itself when searching for containers from position (and disable this filtering). This will give us an opportunity to specify order in case of overlapping drop lists.

also, I prepared a clone of @shesse StackBlitz with a different fix. Fix that overwrites _getSiblingContainerFromPosition to search over custom-defined list of containers/siblings. Like this, you should be able to keep the order you specified and mitigate side effects.

https://stackblitz.com/edit/angular-drag-and-drop-child-groups-9bmj6t?file=app%2Fcdk-drag-drop-connected-sorting-group-example.ts

Hi there all, just want to throw this in for anyone who is doing something similar but whose list isn't a simple two dimensional string array(s). This is based off of ZycaR's stackblitz so I tried to keep all the class names and what not the same for easier reference.

My list contains an array of objects, and those objects can have a child array of objects (but only some of them if they are a category). So I had to do mine slightly different, but struggled with it for a while. Hence why I'm throwing this in for anyone who is struggling to do the same.

<div cdkDropListGroup>
  <div class="example-container">

    <div cdkDropList [cdkDropListData]="features" class="example-list" (cdkDropListDropped)="drop($event)"
      [cdkDropListConnectedTo]="dls">
      <div class="example-box" *ngFor="let item of features" cdkDrag>
        <div *ngIf="!item.isCategory; else arrayView">{{item.name}}</div>
        <ng-template #arrayView>
          <div class="example-container">
            <div cdkDropList [cdkDropListData]="item" class="example-list" (cdkDropListDropped)="childArrayDrop($event)"
              [cdkDropListConnectedTo]="dls">
              {{item.name}}
              <div class="example-box" *ngFor="let innerItem of item.children" cdkDrag>{{innerItem.name}}</div>
            </div>
          </div>
        </ng-template>
      </div>
    </div>
  </div>

</div>

Main thing to note in the HTML is the two different (cdkDropListDropped) functions for main and child arrays. Also had to change innerItem of item to innerItem of item.children (to be clear children is in reference to that object's child list).

drop(event: CdkDragDrop<any[]>) {
    const childArrName = 'children';
    if (event.previousContainer === event.container) {
      moveItemInArray(
        event.container.data,
        event.previousIndex,
        event.currentIndex
      );
    } else {
      transferArrayItem(
        event.previousContainer.data[childArrName],
        event.container.data,
        event.previousIndex,
        event.currentIndex
      );
    }
    this.orderHasChanged();
  }

  childArrayDrop(event: CdkDragDrop<any[]>) {
    const childArrName = 'children';
    if (event.previousContainer === event.container) {
      moveItemInArray(
        event.container.data[childArrName],
        event.previousIndex,
        event.currentIndex
      );
    } else {
      transferArrayItem(
        event.previousContainer.data,
        event.container.data[childArrName],
        event.previousIndex,
        event.currentIndex
      );
    }
    this.orderHasChanged();
  }

The separate functions look for the child array inside of the event.container.data based on what array/object the item is being dropped in to, because the container data can be the object itself, therefore throwing off transferItemArray or moveItemArray code.

I.E. drop() is for moving items in the parent array and dropping from child to parent, and childArrayDrop is for moving items in the child array and dropping from parent to child.

These seem like a really simple fix but it took me a LONG time (embarrassingly long) and lots of browser crashes to figure out.

I'm in no way a cdkDragDrop expert (first time using it really), so I'm sure this could be approved upon. If you have suggestions you can reply, otherwise it works for what I need it for. Next plan is to make it work for a multi dimensional list, since this one is probably stuck to just a two level array.

Edit: I had to make a small addendum to the childArrayDrop function, this handles when an item is drag and dropped from one child to another.

childArrayDrop(event: CdkDragDrop<FeatureList[]>) {
    const childArrName = 'children';
    if (event.previousContainer === event.container) {
      moveItemInArray(
        event.container.data[childArrName],
        event.previousIndex,
        event.currentIndex
      );
    } else {
      if (
        !Array.isArray(event.previousContainer.data) &&
        !Array.isArray(event.container.data)
      ) {
        transferArrayItem(
          event.previousContainer.data[childArrName],
          event.container.data[childArrName],
          event.previousIndex,
          event.currentIndex
        );
      }
      transferArrayItem(
        event.previousContainer.data,
        event.container.data[childArrName],
        event.previousIndex,
        event.currentIndex
      );
    }
    this.orderHasChanged();
  }
wawyed commented 2 years ago

Any updates on this at all? Would be nice to have support for nested drop-lists...

LeahPike commented 1 year ago

Just run into this myself, would be great to have please :)

slavede commented 1 year ago

I have solution. So I have multiple, dynamic nested lists (I really don't know in ahead how many there will be). In the drag&drop code I've noticed they are removing "itself" from siblings list wherever they can :) so I did this:

  public ngAfterViewInit() {
    const dropListRef = this.cdkDropList._dropListRef;
    const originalConnectedTo: any = dropListRef.connectedTo;

    dropListRef['connectedTo'] = ((connectedTo) => {
      originalConnectedTo.apply(dropListRef, [connectedTo]);

      const targetIndex = this.allDropListsIds.findIndex(id => id === this.listId);
      dropListRef['_siblings'].splice(targetIndex, 0, dropListRef);
      return dropListRef;
    }) as any;
  }

Where allDropListsIds is the list of ids for every list we want to be connected Works fine for now :)

BenRacicot commented 1 year ago

@slavede that's great! Could you put together a Stackblitz with that for everyone?

SkoZombie commented 1 year ago

I think @slavede 's work is awesome, but we really need to pressure the CDK team to merge a fix for this. It's been over 3 years now and it's still broken.

Does anyone have any idea who we need to bribe to actually get them to pay attention to closing out bugs rather than focusing on new features?

wupaz commented 1 year ago

Maybe create a gofundme for "bribe dev team" :)

Zodiark1020 commented 1 year ago

I'm using the patch to resolve the "nesting" issue but it has some issues on recognizing the target drop area, in some cases. I'm trying to debug the patch as long as no other solutions come up.

Is there any news about this?

haroldcampbell commented 1 year ago

I have solution. So I have multiple, dynamic nested lists (I really don't know in ahead how many there will be). In the drag&drop code I've noticed they are removing "itself" from siblings list wherever they can :) so I did this:

  public ngAfterViewInit() {
    const dropListRef = this.cdkDropList._dropListRef;
    const originalConnectedTo: any = dropListRef.connectedTo;

    dropListRef['connectedTo'] = ((connectedTo) => {
      originalConnectedTo.apply(dropListRef, [connectedTo]);

      const targetIndex = this.allDropListsIds.findIndex(id => id === this.listId);
      dropListRef['_siblings'].splice(targetIndex, 0, dropListRef);
      return dropListRef;
    }) as any;
  }

Where allDropListsIds is the list of ids for every list we want to be connected Works fine for now :)

This helped me tremendously. Thank you! @slavede

fclmman commented 1 year ago

@slavede solution does not work for horizontal dropLists for some reason

KingR1 commented 1 year ago

I have solution. So I have multiple, dynamic nested lists (I really don't know in ahead how many there will be). In the drag&drop code I've noticed they are removing "itself" from siblings list wherever they can :) so I did this:

  public ngAfterViewInit() {
    const dropListRef = this.cdkDropList._dropListRef;
    const originalConnectedTo: any = dropListRef.connectedTo;

    dropListRef['connectedTo'] = ((connectedTo) => {
      originalConnectedTo.apply(dropListRef, [connectedTo]);

      const targetIndex = this.allDropListsIds.findIndex(id => id === this.listId);
      dropListRef['_siblings'].splice(targetIndex, 0, dropListRef);
      return dropListRef;
    }) as any;
  }

Where allDropListsIds is the list of ids for every list we want to be connected Works fine for now :)

Hi @slavede :) I know quite a lot time was gone... but what are this.cdkDropList and this.listId?

slavede commented 1 year ago

this.cdkDropList is a drop list used inside this component and listId is recursively passed list of every cdk drop list used.

Where component is a wrapper around cdkDropList. Now every inner component receives listIds of every list id used before and then it adds itself to that list. And this listId is passed as reference so every wrapped component gets that

diego-rapoport commented 1 year ago

Any news on this? Couldn't get any workarounds to really work on my case and examples are always at older versions of angular.

FritzHerbers commented 1 year ago

I am not a big user of components, I only needed the drag, stepper and tabs and quickly noticed they fail to have some functionality. https://github.com/angular/components/issues/16671 https://github.com/angular/components/issues/9733 and some others

I opened https://github.com/angular/components/issues/15958 with the hope that with github labels a developer could easily see which issues exist on a specific component and fix them when they are anyway updating the component. I have seen many updates on the stepper and drag component, but none of the issues of interest were fixed.

This issue has an open pull request (January 2021): https://github.com/angular/components/pull/21526 Needs a review: better don't have a fix, as a fix which might not work 100% or break something. Also the unassignment of crisbeto 2 weeks ago, the original developer of this component, is questionable.

Some of the issues I had were from 2018 and still open or closed/unresolved. I have seen several new components added and work was done on performance, but none of my issues of interest were fixed. (Independent of improvements made in those components)

I tried to fix some of the issues, and had a hard time, the code is very cryptic, fails any inline documentation, and you need to know and understand the eco-system of components in depth. This might also be the cause, what i could watch, that development is only crafted by a few developers, compared to the number of users (contributors:700, users:700'000, don't know the accuracy/measuring method of these numbers). Also guidance of a core developer on how to fix an issue, which could evolve to a pull request is somehow missing. Besides that, a lot of pull requests don't even make it to merge, not that they might not be feasible, but of age (closed/unmerged flag and with no reason, when not otherwise solved this is a slap in the face for the PR author).

I appreciate all work done, but lost hope that these issues will ever get fixed and I think only that someone occasionally asks, keeps some of these issues open and not automatically closed by the infamous "closing" bot. I have seen, in my eyes important issues been closed, and have seen some issues being reopened by other authors.

SkoZombie commented 1 year ago

I'm really disappointed that the NG team have ignored long standing bugs like this. I've never looked at the code, but from what @FritzHerbers says, it seems like they have HUGE technical debt they're totally ignoring which is really concerning as someone who sells software that uses Angular.

Hopefully the team can address it. I would encourage anyone who knows people in the angular team, to reach out to them or at least try to get some attention on this and similar issues. If we all try to contact them, they might actually put some focus on some of these old issues.

BenRacicot commented 1 year ago

Well it's not Angular per-say, it's Angular material components. Which has always been very poorly maintained. The CDK is like a 6 out of 10 but Material is like a 2 out of 10 IMO. Use Angular because it's super-pro but seek alternatives to Material for pre-built components.

BenRacicot commented 1 year ago

We've got a working version of the drag-drop CDK with nesting into github.com/Recruitler/drag-drop main. Would love some review and critique/improvement.

MatviiStelmakh commented 8 months ago

@shesse thanks for the workaround, you saved my day

xXNickznXx commented 7 months ago

For anyone using the monkey-patch from https://github.com/angular/components/issues/16671#issuecomment-757043157: 17.0.5 introduced a breaking change by renaming _clientRect to _domRect, so you'll need to do that in the patch too

t0m-4 commented 6 months ago

Thanks @xXNickznXx and @soren-petersen

I update the "Monkey Patch" from @soren-petersen to work with angular@17

nested-drag-drop-patch.zip

MeMeMax commented 5 months ago

I would appreciate an official fix from the angular team.

anthime2010 commented 3 months ago

Hello,

With the idea of @slavede , and this example on stackblitz, I made a Mix of the two and I obtained

 this.dragAndDropService
      .getCdkDropListObservable()
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe((cdkListId: CdkDropList[]) => {
        this.cdkDropLists = cdkListId;
        this.cdkDropLists.forEach((dropList) => {
          let dropListRef = dropList._dropListRef;
          let siblings = this.cdkDropLists.map((dl) => dl?._dropListRef);
          siblings = siblings.sort( (a,b) => a.data.id.localeCompare(b.data.id))
          dropListRef._getSiblingContainerFromPosition = (item, x, y) =>
                siblings.find((sibling) => sibling._canReceive(item, x, y));
            });

        });

I ve got Dnd service, which provide an observable to react on every update, then I overload '_getSiblingContainerFromPosition' to select the correct DropList. The only constraint is to be sure that the parent DropList is at the last place in the siblings list. If it'is not the case , it may provide the top parent (if before whished child drop list). Put the parent at the end of the list (with specific naming of the top parent drop list for example xxx_parent_drop_list)

Thanks to all for your help

shyamal890 commented 3 months ago

Thanks @xXNickznXx and @soren-petersen

I update the "Monkey Patch" from @soren-petersen to work with angular@17

nested-drag-drop-patch.zip

MonkeyPatch not working when the nested element is dynamically rendered with a delay. In such cases .domRef and .container are undefined