angular / components

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

Add cdk drag-and-drop features #8963

Closed jelbourn closed 6 years ago

jelbourn commented 6 years ago

This issue tracks the high-level addition of drag-and-drop features to the cdk.

fxck commented 6 years ago

Is there any design doc I can follow @jelbourn ? I've tried quite a few drag and drop libraries, all of the are bad in one way or another, those that use native APIs are rather limited when it comes to styling the content (which is also often glitched when you drag an item that is partially hidden behind overflow) that is being dragged and those that do it programatically often don't have must have accessibility features like scrolling when the item being dragged is on the side of the scrollable element, so I really wonder how you gonna tackle this.

jelbourn commented 6 years ago

Kristiyan is working on a design now. Any specific feedback you have about what you like/dislike about other libraries would be good to know.

dojchek commented 6 years ago

Most of the libs (with native html dnd) such as angular-dnd, sortablejs, dragula handle DOM on their own which is very inconvinient when using unidirectional dataflow (ngrx). There is no an easy way to cancel these updates and walkarounds feel very hacky. This is the biggest issue with all the libs I've used so far I would love to see the following features:

Would be awesome if we would get something like https://github.com/atlassian/react-beautiful-dnd

nayfin commented 6 years ago

Additionally, most seem designed to simply append elements onto lists of other elements when dropped into a drop zone. I would love to see a "canvas" mode implemented to simplify the dropping into an absolute position of an element. This would be extremely useful for design oriented apps, where the user is building something from smaller elements. I used interactjs years ago and really appreciated all it's features ( touch support, inertia, grid snapping, svg support ). It's an older library but could prove useful at least for feature ideas. I'm ready to contribute in any way I can once you all have laid the plans and groundwork.

Tiedye commented 6 years ago

As mentioned dojchek full support for state management like ngrx/redux is very important. We don't yet have an angular dnd library that does.

jelbourn commented 6 years ago

What would be an example of a scenario w/ ngrx interplay with drag-and-drop?

dallastjames commented 6 years ago

Not sure if this is quite the example you're looking for but I have a site that I maintain that uses drag and drop to create a data sorting functionality. We built it to be closely tied into ngrx where each of the containers (items to be sorted and a container for each category is populated via ngrx store selector. When the user drags an item from one list to another, we cancel all the default drag/drop actions and instead fire a ngrx event that updates the object in question with the new category and let the container selectors "move" the item (this allows for some other things like firing save events and such).

Having something built into the cdk that could facility the drag/drop actions and allow me to intercept at certain events (ie. onDrop) to do things like fire ngrx action and cancel any further drag/drop effects would be great.

reppners commented 6 years ago

I've ported an angularjs library but intentionally just implemented drop zones to be as general purpose as possible. By having a general purpose drop zone all the ngrx use cases should be possible as well because the library just informs you something has been dropped. What should happen with that event is all up to the developer.

https://github.com/reppners/ngx-drag-drop

So if you're asking for features I'd like to see for CDK than all of what the mentioned library does because it's quite basic (does not do the lists or sorting stuff internally) but configurable to cater all use cases.

Wether it's implemented with native HTML5 drag-and-drop or implementing it all in the angular stack is up to you, but what I'd like to be possible is drag stuff from the outside of the browser into an app. This is just possible with native drag-and-drop.

benlesh commented 6 years ago

It's probably important to come up with a list of drag, and drag and drop related use cases. What sort of things are we dragging? What sort of drop targets are we going to have? I suspect there's probably more permutations of the latter: single drop targets, groups of drop targets, nested drop targets. There's also a variety of ways that you might want to execute logic when interacting with those targets while dragging or dropping.

ngrx

FWIW, I wouldn't worry about Redux or ngrx when it came to current drag state. That's really a UI concern, and not something I would do as critical to persist to state.

manju-reddys commented 6 years ago

Interesting library written in ES6 Drag and Drop lib

Tiedye commented 6 years ago

If we could have this: https://github.com/react-dnd/react-dnd for angular, I would be very satisfied.

samherrmann commented 6 years ago

I am working on drag and drop in an Angular app this week. @dojchek already mentioned most of the features required for my use case, but here are a couple more:

dirkluijk commented 6 years ago

Would be great!

On our teams wish list:

Another trick/feature is to have the center of the ghost element as reference for the drop behaviour (instead of the pointers position). E.g. you start dragging an element by "grabbing" it at its left side, and when the center of the dragged element covers the dropzone, but your pointer is not hovering it, you still want to be able to drop it in the dropzone?

By the way. When sharing ideas, why not sharing Stackblitzes? https://draggable-with-anchors-lucas-8eq4ht.stackblitz.io 😄

dirkluijk commented 6 years ago

Today I found some time to work out some ideas. Please see: https://stackblitz.com/edit/draggable-system

What is in it:

The directives:

Just wanted to drop my ideas here...

Tiedye commented 6 years ago

@dirkluijk It it supposed to be interactive? I had a look and nothing was draggable, is it supposed to be?

dirkluijk commented 6 years ago

Oops, forgot to add polyfill for pointer events. I guess you checked on a mobile browser. Should work now!

Tiedye commented 6 years ago

Nah just Firefox, seems they don't have it enabled by default yet.

On Feb 16, 2018 6:02 PM, Dirk Luijk notifications@github.com wrote:

Oops, forgot to add polyfill for pointer events. I guess you checked on a mobile browser. Should work now!

— You are receiving this because you commented. Reply to this email directly, view it on GitHubhttps://github.com/angular/material2/issues/8963#issuecomment-366383519, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AF5opZI0f_njRwOL1dNdty2Guf1qVYD5ks5tVgkbgaJpZM4Q_jF0.

nayfin commented 6 years ago

Firefox worked for me. @dirkluijk Nice work! That looks better than most Angular drag and drop libraries I have found. I'm happy to see that we have so many folks chompin' at the bit to contribute to this. I think this project will mature rapidly.

dirkluijk commented 6 years ago

Here is a second version with some droppable ideas demonstrated: https://stackblitz.com/edit/draggable-system-2

Tiedye commented 6 years ago

@dirkluijk looks like the polyfill you added didn't stick, it works great when I do add the polyfill though!

dirkluijk commented 6 years ago

@Tiedye My mistake. Updated both Stackblitz demos!

FDIM commented 6 years ago

I didn't see this one mentioned anywhere: http://kamilkp.github.io/angular-sortable-view/

Few forks down it works ok for our use cases - I had to implement auto scrolling on any number of scroll-able parents though and fix it on mobile on latest chrome (thanks to passive flag). It is also working nicely in IE 11. On the other hand the version we have in the project is cleaned up a bit more and I think I could make it public if it would help anyone.

Sadly it's only available for angularjs and doesn't seem to be maintained. Maybe some ideas / code could still be reused? It is fully declarative after all. That aside, I have to find / make an alternative as well as we are in the process of migrating. Personally I'd would try to rewrite that - as others outlined, there aren't that many good ones out there.

fxck commented 6 years ago

I think it would be a good idea to wait and see what @crisbeto has in store, how far in development / design process he is.

FDIM commented 6 years ago

@crisbeto in case there is no progress on this feature, I've refactored angular-sortable-view to typescript and made it work with angular 5 (and angularjs) I could try to push it and see if we can contribute.

samherrmann commented 6 years ago

I have a little prototype over here. If you want to play with it I suggest doing so not on a smartphone since I didn't put much effort into the styling of the demo app and it is not mobile friendly.

dirkluijk commented 6 years ago

I am planning on releasing an early version of an Angular library next week. Based on https://stackblitz.com/edit/draggable-system-2.

Of course I hope its ideas will be valuable for a CDK package.

manju-reddys commented 6 years ago

@dirkluijk, Looking forward!

sharikovvladislav commented 6 years ago

Very nice work @dirkluijk

nicky-lenaers commented 6 years ago

@dirkluijk Anything you can show us? Eager to see some demo of how this is going to work!

cengiz-dev commented 6 years ago

Hello all,

Inspired by @dirkluijk and others, I have put together the following library:

https://github.com/cengiz-dev/angular-drag-and-drop

Demo:

https://stackblitz.com/github/cengiz-dev/angular-drag-and-drop

Let me know what you think.

Cheers

dirkluijk commented 6 years ago

My most recent work is visible on

https://stackblitz.com/edit/draggable-part-5 and https://stackblitz.com/edit/draggable-part-6.

It is the code behind this tutorial: https://www.youtube.com/playlist?list=PLJpL6ImpHi2i1ALq7-x7zRADoole7eq8U.

Next challenges:

I was planning on extracting to a library as well

@cengiz-dev will take a look at your code as well. Expect my feedback in a day or two.

elvisbegovic commented 6 years ago

@dirkluijk your example6 sucks with relativly big list (even with virtual scrolled) https://stackblitz.com/edit/draggable-part-6-ovo5rl?file=app/app.component.ts

dirkluijk commented 6 years ago

Whoa, easy. It does not suck, but wasn't yet optimised. Please see this example where it performs a lot better: https://stackblitz.com/edit/draggable-part-6-ycchkt?file=app%2Fapp.component.html

The trick is to handle all pointermove logic outside Angular (using ngZone.runOutsideAngular()). This is a known trick which is used inside Angular CDK as well.

dirkluijk commented 6 years ago

To give an update on the rest of the drag/drop ideas: I am currently struggling with connected sortable lists. The idea is that you can move elements between multiple lists on the fly, e.g. with a single drag operation.

Of course, I can just destroy the old element (view) and create a new one using Angulars ngFor directive. However, in combination with dragging, this is quite hard. On Safari this won't work with the pointer events polyfill because the browser stops emitting pointer events when the original element is destroyed. Also, it would be nice if the original draggable directive remembers its state when moved to another location accross the DOM.

So...

I am looking for a clean way to render multiple lists with a single ng-template and to move views accross multiple view containers. This can be quite hard, because you would have to reuse IterableDiffer logic which is used by Angulars ngForOf and combine it with multiple iterables.

For example: when change detection occurs, and iterable 1 has an item deleted and interable 2 has an item added, this means that the original view should be reused and a ViewContainer.detach() should be performed together with a ViewContainer.insert().

nayfin commented 6 years ago

@istiti criticism should always be constructive, not vague, negative and lazy.

@dirkluijk identified a need many in this community have and has shared his solution as well as offered instruction on how build your own implementation, a rare gift and sign of the strength of this community. Legitimate issues with his solution should include specifics ( at what size does it start to suck? ), and hopefully some proposed solution.

Adolescent insults towards contributions is destructive to our community and will not be tolerated.

tibortotok commented 6 years ago

Nice progress @crisbeto . Checked the demo and found a minor glitch. If the handle is released without moving the element (like a double click for example) sometimes the preview element freezes and the item cant be dragged anymore. Trying to find out why.

Is there a plan to support sorting nested lists both with vertical and horizontal layout? Tried to create a horizontal list of items using float and tried it with flex layout, but sorting is not working properly. I would like to create a kind of configurable dashboard something like this:

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

crisbeto commented 6 years ago

@tibortotok https://github.com/angular/material2/pull/12104 got in a couple of hours ago which adds support for horizontal sorting.

tibortotok commented 6 years ago

Thanks @crisbeto, that sounds great! :)

dirkluijk commented 6 years ago

My newest ideas: https://stackblitz.com/edit/draggable-release-candidate

I finally managed to get the connected sortable list working. Still have issues on Safari. What I like of this approach is that the helper is an actual ng-template so that you can customize its appearance and content.

nicky-lenaers commented 6 years ago

@dirkluijk Thanks for the good work! Just for feedback, I noticed the connected sortable list sometimes duplicates a block when dragging from one list to the other. Tested on mobile Android 8.1.0 (so touch) with Chrome 67.0.3396.87.

jorroll commented 6 years ago

@crisbeto, some feedback on the current cdk-experimental/drag-drop, which may or may not be useful to you:

1

The use of @Input() connectedTo: CdkDrop[]; for linking drop zones isn't the easiest API to work with. For example, say I have a large list of volunteers and a large list of roles the volunteers can be assigned to (both rendered with ngFor).

Currently, I think you need to use @ViewChildren(RoleEntry) roleEntries in the parent component to grab all the role entry components and provide them as a [connectedTo]="roleEntries?.toArray()" input to the volunteer list (CdkDrop) component.

The main problem is that, since the role drop zones are created with ngFor, you cannot pass them as an array to the connectedTo input of the volunteer's list using a template reference variable.

A more user developer friendly solution might be to assign a string label to each drop zone. basically allowing something like "this volunteer entry (i.e. draggable) can be dropped on a zone labelled 'role' OR a zone labelled 'volunteer' (<-- for reordering) OR a zone labelled 'shift'". I think the ng2-dnd library makes use of a strategy like this.

2

The fact that CdkDragHandle must be a ContentChild of CdkDrag can be...annoying. Say I have a custom list component <my-list></my-list> and I have several different custom list entry components (<my-person></my-person>, <my-pet></my-pet>, etc) that can go within it. I may use the components like so (assuming <my-list> extends CdkDrop)

<my-list>
  <my-person *ngFor="let person of people" [person]="person" cdkDrag></my-person>
</my-list>

<my-person> already has, within its view, its own list of context buttons. I'd like to place the CdkDragHandle on one of those buttons, but CdkDrag wants me to do this:

<my-list>
  <my-person *ngFor="let person of people" [person]="person" cdkDrag>
    <button mat-icon-button cdkDragHandle><mat-icon>reorder</mat-icon></button>
  </my-person>
</my-list>

If there are many instances of <my-list><my-person></my-person></my-list> in an app, needing to insert the cdkDragHandle as ng-content can get annoying.

A workaround is to use @ViewChildren(CdkDragHandle) within <my-person> to grab the CdkDragHandle from within the view of <my-person>, then inject the instance of CdkDrag into <my-person>, then manually set the CdkDrag _handles property equal to the ViewChildren query. Something like:

  @ViewChildren(CdkDragHandle) _handles: QueryList<CdkDragHandle>;

  constructor(
    @Inject(CdkDrag) @Self() @Optional() public drag: CdkDrag,
  ) {}

  ngAfterViewInit() {
    this.drag._handles = this._handles;
  }

This strategy isn't ideal.

3

Currently, it's not possible (at least easily) to drop a draggable onto a drop zone and not transfer the draggable. Sometime's I'd like to simply use a drop to pass hidden data / trigger an output event while returning the dragged component to its original position.

4

In the same way that CdkDrop provides an @Input() data: T property for arbitrary data, it would be nice for CdkDrag to have an @Input() dragData: T property for arbitrary data.

Thanks!

Anyway, I realize that DragDropModule is a work in progress. It seems very likely some or all of these changes are already planned, but figured I'd call them out now as sore spots regardless.

Thanks!

fxck commented 6 years ago

So far I have only one problem with cdk-experimental/drag-drop and that is that it doesn't scroll the parent element with overflow: auto; so you have to drag, drop at the top, scroll, drag, drop at the top..

dirkluijk commented 6 years ago

@thefliik:

For issue 1 maybe it's better to have some kind of "connectedGroup" parent directive to connect multiple directives?

For issue 2: I recognise that issue from the project where we built our own d/d solution. The limitation is that @ContentChildren does not query inside child component views. It could be solved by reversing the dependency: the CdkHndle gets the CdkDrag injected and triggers it. But in my solution this was not very easy to refactor because of.. several reasons.

What about MyPersonComponent extends CdkDrag? Then it is much easier to connect things. As a matter of fact, every <my-person> should aways be draggable, since you embed a handle in its view. ;) If not, then the handle should be external, or part of its content.

smnbbrv commented 6 years ago

Depending on extends on a component level in a library is normally not a good idea because the library consumers might want to extend their own classes for the reasons that are way bigger than just drag and drop. Then usually it becomes a pain to choose what of those 2 parent classes to extend which in the end will make people crying

jorroll commented 6 years ago

@dirkluijk I think the solution to issue 2 is to have CdkDragHandle inject the parent CdkDrag directive and register itself as a handle with the parent. This way, both use cases (i.e. View and Content) are accommodated. e.g.

export class CdkDragHandle {
  constructor(
    public el: ElementRef<HTMLElement>,
    @Inject(forwardRef(() => CdkDrag)) private dragDirective: CdkDrag,
  ) {}

  ngOnInit() {
    this.dragDirective._handles.push(this);
  }
}

I believe this strategy would (ahem) drop (😃) easily into the current CdkDrag implementation, which doesn't check it's handles until dragging begins.

Regardless of how it's done though, I'm confident in the material team's abilities to solve this annoyance if they decide they wish to do so.

What about MyPersonComponent extends CdkDrag?

It's already extending another class :-/ (a.k.a. @smnbbrv is exactly correct). But in general you are right, "every <my-person> should aways be draggable."

Regarding issue 1, I imagine there are a number of different approaches and I'm not particularly invested in any strategy other then to call out the fact that the current api is lacking. I'd probably start by trying to give each droppable a label (which I suppose could be a symbol, if strings make folks uncomfortable) and registering them with a service that can automatically connect them based on the labels. Labels seem like the most developer friendly approach while still being flexible, but I'm sure @crisbeto has given this a lot more thought.

dirkluijk commented 6 years ago

Regarding issue 1, something like this?

<!-- draggable with group 'foo' -->
<div cdkDrag [cdkDragGroup]="'foo'"></div>

<!-- dropzone which accepts 'foo' and 'bar' -->
<div cdkDropzone [cdkDragGroups]="['foo', 'bar']"></div>

Regarding issue 2: sounds ok.

However, I must say that having interaction between a directive outside a component with a directive inside a component feels somehow a bit dirty. Shouldn't they both live either in the same component, or outside it (for example in ng-content)?

e.g.

<!-- app.component.html -->
<foo-component cdkDrag>
  <div cdkDragHandle></div>
</foo-component>

I am not sure.

zverbeta commented 6 years ago

The current version cdk-experimental/drag-drop does not work well with flex-layout

P/S Maybe it makes sense to make all the issues in "Projects" as with virtual scroll

FDIM commented 6 years ago

Having refactored angular-sortable-view to angular directives internally and seeing the discussion lately here, it's quite clear that people want something that was done before.

Maybe it would still be worthwhile to check their api and try to cover the functionality described there? Custom helpers, placeholders, groups, auto scrolling are all in there and the same pattern works well as angular directives with no need to extend custom classes (I am surprised to see this actually).

I'm fairly sure I could convince my manager to allow me to open source this internal lib and continue to contribute if it could become part of cdk as this is what we rely on and are quite happy with (overlays and tables are great!)

jorroll commented 6 years ago

@FDIM I imagine people would find value in that. Personally, I've already found a DnD library I'm quite happy with. I'm providing feedback not because I need these features now, but because I'd like to see CdkDragDrop succeed.

The library I'm using is AngularSkyhook, an implementation of the excellent react-dnd library (apparently used by Trello) to angular. Apparently, because the core of react-dnd was implemented as pure javascript (i.e. react-dnd-core), and doesn't even make use of the DOM, the library is particularly portable to many environments. Some examples of what AngularSkyhook can do (all written in Angular).

It's definitely a lower level library then what many may want (for instance, it does not come packaged with a sortable directive), but it has the benefit of relying on a very mature base and I'm finding it to be very flexible.

fxck commented 6 years ago

That looks pretty good. How much does it add to the bundle size?