This is a tracking issue for the sortable functionality.
It won’t be part of the core package, it will be separate. At this stage I’m looking for feedback and comments on API design. Please 👍🏻 if you want to beta test the basic directive/component stuff.
What is it at the moment?
The best intro to the WIP functionality is reading the Kanban example source code. A quick rundown:
- It's currently called
angular-skyhook-card-list
, but this name is bad
- CardListDirective
- Attach to DOM container element
- Expects a list ID, a SortableSpec, and a DND type (like drag source / drop target types)
- Expects you to render the list emitted by SortableSpec.getList inside it, each item having
[cardRenderer]="cardList.contextFor(data, index)"
- Expects you to attach the cardRenderer's
source
to the DOM
- Handles empty list case
- CardListComponent
- Extends the directive, but makes it a little easier by handling the ngFor and the cardRenderer context.
- Accepts cardTemplate as a content child to facilitate this
- SortableSpec
- DIY list reordering. You control everything, so you can use Immutable or NgRx or whatever.
- kinda like DragSource + DropTarget, but gives you monitor.getItem directly, and only calls hover when you need to render the placeholder somewhere else.
- New: NgRxSortable + SortableAction (see below)
SortableSpec
The main factor to consider in decision-making is that implementing SortableSpec according to the needs of the library as it exists now is difficult. The library is fundamentally more flexible than the alternatives which all do at least some array work themselves (ex. Ng2-Dragula, 100% of it; Cdk-experimental, apparently the hover states are handled).
It probably isn't possible to handle all the hover states internally / simplify the spec to just confirmed moves. How would you do that while supporting arbitrary ngFor/deep children/some elements fixed? The relevant CDK code excludes some of these possibilities in its internal implementation — it just runs insertBefore with some prerendered Dom.
SortableSpec requires, currently:
- trackBy should give unique outputs / be injective like a good trackByFn
- Must produce a list with dragged element still present in beginDrag, or not output any change at all
- Must handle multiple listIds, meaning there’s a minimum backing store complexity of
{ [k: string]: Data[] }
or at least a list of lists (like the Kanban example)
- Must be able to revert on endDrag
- Must hover or drop in location described by hovering item, without losing 2
- ANY modifications must be presented through an Observable (probably a BehaviorSubject or similar) producing a new value
- Optional: Correctly handle items dragged from external sources.
2 means simple array mutations are out, and slice(0) is in. 6 is a hurdle because few people in the wild are comfortable enough with Rx to do this on their own. Nevertheless, every item can be tricky if you attack the problem blindly.
Copy/clone support
Should SortableSpec include copy/clone functionality? Currently those features appear to require internal support from the directives due to Chrome bugs. Needs a second look.
If you add copy/clone, there are actually three more requirements for SortableSpec implementations:
- copy returns boolean, mainly a burden if you’re trying to be generic
- clone(x) -> x’ returns x’ !== x, trackBy(x’)!==trackBy(x)
- Hard part: Check if item.isCopy during beginDrag/hover/drop, and completely change behaviour. May be coalesced with external drags functionality, since it’s kinda similar. JIT strategy below makes this easier.
SortableSpec implementation strategies
There are two main strategies I’ve used so far.
Direct (not very good)
- keep a canonical copy, call it beforeDrag
- produce a new list each time by immutably moving the card from the beforeDrag structure to a new one on each hover.
hover() { current = move(beforeDrag, ...) }
drop() { beforeDrag = move(beforeDrag, ...); }
Pros: no NgRx needed.
Cons: much more complicated to write, and difficult to get bug-free, especially when supporting externals and clones. Half of the moveCard function is in an if block.
Just-In-Time
Similar to react-sortable-tree. See Kanban again.
- keep a canonical data store intact
- Clone store + remove item in beginDrag, but keep a reference to the item
- Re-add it on-demand ('JIT'), in a selector.
Pros: Simple hover implementation that just updates the item to be inserted later. Simple handling of externals and clones; just don’t delete the original and proceed as usual.
Cons: kinda need NgRx and all that baggage.
(Side note: could you do step 3 of JIT without requiring users to implement it themselves? Probably not. We want to support Immutable.js, for example, which doesn't have the same insert API as arrays.)
Shipping SortableSpec implementations?
As far as I can see, there are no satisfactory reasons for actually shipping any complex SortableSpec implementations. Any such implementation would need to be complete, dependency-free (manual immutables, no NgRx), and configurable (how? The directives I wrote to do this are really bad, and there’s no easy way to make a spec that updates a component variable without weird callbacks or subscriptions.). That would be a maintenance nightmare, and because the code would look really scary (see: the other requirements), users would make feature requests instead of building features themselves.
I also really don’t want people using whatever spec that’s available as an alternate data store. Sortable should work with your existing data. The Kanban example store is great because it takes existing data and builds a spec that simply interacts with that, and you have complete control.
On the other hand, people don't typically want to use more than one dnd library, as it has a cost on bundle size. There is a mid-point between extensibility and ease of use that might not be covered without any shipping specs that are easy to use.
SharedSortableSpec -- potential
The most promising spec to ship would be the ng2-dragula replacement, SharedSortableSpec. It would probably be rewritten to resemble a mini-NgRx with the JIT strategy, and the directive that applies it would be subsumed within CardListDirective so it wouldn't be nearly as hacky.
Pros: worthy replacement for ng2-dragula and approximately as simple as all the other solutions out there.
Cons: maintenance, non-extensibility, and feature creep. Basically, all the reasons why you wouldn't use other libraries. Because it's easier than using ngrx, people would choose it as the default, and wouldn't be able to add features.
NgRX helpers (edit: DONE)
I have one last idea for what to ship:
- An NgRx Action class, including the following
- event type (beginDrag, hover, etc)
- item type
- DraggedItem object
- (instead of the silly one class per event-itemtype!)
- A SortableSpec with customisable trackBy that takes a Store, and dispatches that action when the spec is called
This would remove about 50 lines of boilerplate from the kanban demo. More importantly, it would represent a focus on advanced NgRx-based use cases and not simple one-component lists, for which there are abundant easier alternatives.