0

I am working on a kanban style drag&drop system with ng2-dragula. And I have an issue, and I think it's because every time you drop an item to a new place it sends the data to the server and redo the whole list of items that you can drag around. And if you do it fast enough you can break the drag&drop cycle. Is there a way to limit the intervall you can make an API call? Similar to RxJS debounceTime, but since the list is almost always changing I cannot pipe a filter to it.

Basic constructor and drag event subscribtion:

constructor(private dragulaService: DragulaService, ) {
    this.makeUndragabbles(); 
    this.subs.add(this.dragulaService.dropModel('cardList')
      .subscribe(({ item, target, source }) => {
        const dragObj: DragObject = {
          item: item,
          stageId: target['id'],
          name: this.pipelineConfig.name
        };
        this.modifyStage(dragObj);
        const drake = this.dragulaService.find('cardList').drake; //debug variable
        const sourceModel = drake.models[drake.containers.indexOf(source)]; //debug variable
      }));
  }

First it was for making non draggable items, not it's a bit more:

private makeUndragabbles() {
    if (!this.dragulaService.find('cardList')) {
      this.dragulaService.createGroup('cardList',
        {
          copy: false,
          revertOnSpill: true,
          moves: (el, container, handle, sibling) => {
            return !el.classList.contains('nodrag');
          },
          isContainer: (el) => {
            return el.classList.contains('stage');
          }
        });
    }
  }

Dragged item emitting function:

private modifyStage(draggedItem) {
    this.drag.emit(draggedItem);
  }

Rest call function:

   private saveDraggedItem(pipelineType: string, statusChangeDTO: StatusChangeDTO) {
        if (pipelineType === 'dealStages') {
          this.dealService.savePipelineDealStageUsingPOST(statusChangeDTO).pipe(
            debounceTime(1000),
          )
            .subscribe(res => { }
              , (err) => this.error.emit(err)
              , () => { this.getAllDealsForPipeline(); });
        }
      }

Emitted data cacher:

  drag(draggedItem: DragObject) {
    if (draggedItem.item) {
      const statusChange: StatusChangeDTO = {
        id: draggedItem.item.id,
        newStatusId: +draggedItem.stageId
      };
      this.saveDraggedItem(draggedItem.name, statusChange);
    }
  }
Exitl0l
  • 459
  • 2
  • 11
  • 27
  • You could always try and write an interceptor, but that would be pretty dirty. This sounds like a problem with Dragula. Any reason to not use the Angular Material CDK? – Will Alexander Aug 07 '19 at 13:25
  • @WillAlexander yeah the project i am working on is a company project and everything is done with bootstrap styles. I suggested to use that one, but it got rejected. But eventually that may be the case. I've tried to work around the original dragula file, since it lacks undefined and/or null-checks, and i got it to a somewhat working stage but it suddenly copies the item when the error occures. So i end up with the same item duplicated in one of the lists (if i drop the original one on the list it's disappearing) but it's annoying – Exitl0l Aug 07 '19 at 13:38
  • The other option is to write your own drag & drop. It's reinventing the wheel, but it would let you handle everything exactly the way you want, and if you know RxJS well, it's not too difficult… – Will Alexander Aug 07 '19 at 13:42
  • The consuming component is using it well, so if it's not a must i'd not reinvent the wheel. Maybe dragula wasn't the best choice for this type of d&d task. But i try my best to counter the issue i am facing. However if i do a debounceTime on the rest call itself it's not really working. – Exitl0l Aug 07 '19 at 13:48
  • How does Dragula subscribe to your HTTP call? – Will Alexander Aug 07 '19 at 13:53
  • Dragula basically just emits the dragged item then I make some modifications gather data that's needed then call a swagger generated service. Long story short. But i can give you some snipets if it helps – Exitl0l Aug 07 '19 at 13:56
  • If it emits it as an event, you can use RxJS's `fromEvent` operator to turn it into an Observable. That would allow you to add all the operators you like before forwarding it to the service. – Will Alexander Aug 07 '19 at 13:57
  • If it doesn't use an event, you can always have it call `next` on a Subject and pipe your operators there. – Will Alexander Aug 07 '19 at 13:58
  • @WillAlexander I've added snippets for better understanding ;) – Exitl0l Aug 07 '19 at 14:04
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/197619/discussion-between-will-alexander-and-alex-bene). – Will Alexander Aug 07 '19 at 14:09

2 Answers2

1

Here's one possible implementation:

  • turn drag into an EventEmitter, maintaining your modifyStage method
  • create a destroy$ Subject which emits in ngOnDestroy

Then in ngOnInit:

this.drag.pipe(
  takeUntil(this.destroy$),
  debounceTime(1000),
  filter(item => !!item.item)
  map(item => {
    const statusChange: StatusChangeDTO = {
      id: draggedItem.item.id,
      newStatusId: +draggedItem.stageId
    };
    return { name: item.name, status: statusChange }
  }),
  filter(data => data.name === 'dealStages'),
  concatMap(data => this.dealService.savePipelineDealStageUsingPOST(data.status))
  // depending on requirements, perhaps use switchMap or exhaustMap
).subscribe();

While this is not totally complete, I think it illustrates my approach. What do you think?

Will Alexander
  • 3,425
  • 1
  • 10
  • 17
0

Since the whole process uses more than one component I managed to get this done in one. With the help of @Will Alexander and this post:debouncing EventEmitter

Final solution is:

subs = new Subscription();
debouncer = new Subject();

Added this into constructor

this.subs.add(this.debouncer.pipe(
      debounceTime(500))
      .subscribe((val) => this.drag.emit(val)));

And unsubscribe in ngOnDestroy

 ngOnDestroy() {
    this.subs.unsubscribe();
  }
Exitl0l
  • 459
  • 2
  • 11
  • 27
  • But it introduces a new issue. If I drag more than one item within the debounceTime only the last one gets saved due to the behavior. Any hints on how can i counter that? – Exitl0l Aug 08 '19 at 08:19