How to use Angular CDK DragDrop with predefined slots?

Tags: , , ,



StackBlitz: https://stackblitz.com/edit/league-predictions

I have a project where I want to predict football league standings. I have two lists, one where the prediction goes, and one with all the teams.

At the beginning the prediction list is empty, so you can start dragging teams in. But because it is empty, the first team is automatically ranked first. Of course you can sort them later, but what I want is predefined slots based on the number of team. This way you can drag the teams directly in the right place.

I can’t really find a solution on the internet on how to achieve this.

This is my current situation, so you can see what I am talking about: League Predictions

And this is what I want to achieve.enter image description here

Does someone know how to predefine slots for Angular CDK DragDrop

This is my current code.

<div class="container">
  <div class="example-container">
    <h5>Predictions</h5>
    
    <div
      cdkDropList
      #predictionsList="cdkDropList"
      [cdkDropListData]="predictions"
      [cdkDropListConnectedTo]="[teamList]"
      class="example-list"
      (cdkDropListDropped)="drop($event)"
    >
  
      <div class="example-box" *ngFor="let prediction of predictions; let i = index" cdkDrag>
        <app-team [team]="prediction" [index]="i"></app-team>
      </div>
  
    </div>
  </div>
  
  <div class="example-container">
    <h5>Teams</h5>
  
    <div
      cdkDropList
      #teamList="cdkDropList"
      [cdkDropListData]="teams"
      [cdkDropListConnectedTo]="[predictionsList]"
      class="example-list"
      (cdkDropListDropped)="drop($event)"
    >
  
    <div class="example-box" *ngFor="let team of teams;" cdkDrag>
      <app-team [team]="team"></app-team>
    </div>
  
    </div>
  </div>
</div>

Don’t mind the long list of team, this will all be data from database

import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
  
  constructor() { }

  ngOnInit(): void {
  }

  predictions = [
  ];

  teams = [
    {
      name: 'ADO Den Haag',
      logo: 'Ado-Den-Haag-Logo.png'
    },
    {
      name: 'Ajax',
      logo: 'AFC-Ajax-Logo.png'
    },
    {
      name: 'AZ',
      logo: 'AZ-Alkmaar-Logo.png'
    },
    {
      name: 'FC Emmen',
      logo: 'FC-Emmen-Logo.png'
    },
    {
      name: 'FC Groningen',
      logo: 'FC-Groningen-Logo.png'
    },
    {
      name: 'FC Twente',
      logo: 'fc-twente-logo.png'
    },
    {
      name: 'FC Utrecht',
      logo: 'FC-Utrecht-Logo.png'
    },
    {
      name: 'Feyenoord',
      logo: 'Feyenoord-Rotterdam-Logo.png'
    },
    {
      name: 'Fortuna Sittard',
      logo: 'Fortuna-Sittard-Logo.png'
    },
    {
      name: 'Heracles',
      logo: 'Heracles-Almelo-Logo.png'
    },
    {
      name: 'PEC Zwolle',
      logo: 'PEC-Zwolle-Logo.png'
    },
    {
      name: 'PSV',
      logo: 'PSV-Eindhoven-Logo.png'
    },
    {
      name: 'RKC Waalwijk',
      logo: 'rkc-waalwijk.png'
    },
    {
      name: 'SC Heerenveen',
      logo: 'SC-Heerenveen-Logo.png'
    },
    {
      name: 'Sparta Rotterdam',
      logo: 'Sparta_Rotterdam_logo.png'
    },
    {
      name: 'Vitesse',
      logo: 'Vitesse-Arnhem-Logo.png'
    },
    {
      name: 'VVV Venlo',
      logo: 'VVV-Venlo-Logo.png'
    },
    {
      name: 'Willem II',
      logo: 'Willem-II-Logo.png'
    },
  ];

  drop(event: CdkDragDrop<string[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      transferArrayItem(event.previousContainer.data,
                        event.container.data,
                        event.previousIndex,
                        event.currentIndex);
    }
  }

}

Answer

made a stackblitz outlining what to do, first, create dummy entries in your predictions array:

  getPrediction(): Team[] {
    let localStorageItem = JSON.parse(localStorage.getItem(this.league.name));
    return localStorageItem == null ? this.getTeams().map(t => ({})) : localStorageItem.standings;
  }

this will fill out your slots. disable dragging on these items:

<div class="example-box" *ngFor="let prediction of predictions; let i = index" cdkDrag [cdkDragDisabled]="!prediction.name">

next you need to add a parameter to your drop function to know which container is being dropped on:

drop(event: CdkDragDrop<Team[]>, droppedOn: 'teams' | 'predictions') {

and template updates appropriately:

(cdkDropListDropped)="drop($event, 'predictions')"

 ...

(cdkDropListDropped)="drop($event, 'teams')"

in your drop function, you use this parameter in the case of a list transfer, to either add or remove a placeholder:

  if (droppedOn === 'teams') {
    // moved back to teams, need to re add placeholder
    this.predictions.push({});
  } else {
    // otherwise, removing a placeholder
    // find the idx to remove, first placeholder at or below current idx
    let removeIdx = this.predictions.findIndex((t, i) => i >= event.currentIndex && !t.name);
    if (removeIdx < 0) {
      // or just the first available.
      removeIdx = this.predictions.findIndex(t => !t.name);
    }
    this.predictions.splice(removeIdx, 1);
  }
  transferArrayItem(event.previousContainer.data,
                    event.container.data,
                    event.previousIndex,
                    event.currentIndex);

maybe some tweaks / improvements could be made to the algorithm to determine which blank to remove in the case of dropping a team on a team or where to insert the blank on switch back to teams, but the simple version here worked well enough when i played with it.

working blitz: https://stackblitz.com/edit/league-predictions-ouu8nr?file=src%2Fapp%2Fprediction%2Fprediction.component.ts



Source: stackoverflow