Angular: Enhancing user experience with draggable and droppable items in a flexible list

My issue involves drag and drop divs within a list. However, the divs seem to move in an unpredictable manner. They do not go where I intend them to be placed.

Here is the TypeScript code:

timePeriods = [
    '1', '2', '3', '4', '5', '6', '7'
  ];

  drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(this.timePeriods, event.previousIndex, event.currentIndex);
  }

And here is the HTML code:

<div cdkDropList cdkDropListOrientation="horizontal" class="list" (cdkDropListDropped)="drop($event)">
<div class="box" *ngFor="let timePeriod of timePeriods" cdkDrag>{{timePeriod}}</div>

As for the CSS styling:

.box {
  width: 33%;
  border: solid 1px #ccc;
  margin: 3em;
}

.list {
  width: 100%;
  border: solid 1px #ccc;
  height: 90%;
  display: flex;
  flex-direction: row;
  background: white;
  border-radius: 4px;
  overflow: hidden;
  justify-content: space-around;
  flex-wrap: wrap;
}

https://i.sstatic.net/NSQcx.png

Answer №1

This content overlaps with a discussion on Angular CDK drag and drop issue inside CSS flexbox.

As mentioned in the aforementioned thread, my suggestion is to establish an items table matrix that mirrors your flex list. For a comprehensive explanation, please refer to this post: 'n'drop-mixed-orientation-in-flex-row-wrap/

I have devised a solution for your issue on StackBlitz, accessible here: https://stackblitz.com/edit/angular-drag-and-drop-in-flex-list

Structure:

<div #rowLayout cdkDropListGroup>
  <!-- By assessing the width of the row layout and the item box, you can determine the number of columns per row, initializing an items table matrix accordingly -->
  <div
    *ngFor="let itemsRow of getItemsTable(rowLayout)"
    cdkDropList
    class="list"
    cdkDropListOrientation="horizontal"
    [cdkDropListData]="itemsRow"
    (cdkDropListDropped)="reorderDroppedItem($event)"
  >
    <!-- Dropping reorganizes the items and recalculates the table matrix -->
    <div class="box" *ngFor="let item of itemsRow" cdkDrag>
      <div class="drag-placeholder" *cdkDragPlaceholder></div>
      {{ item }}
    </div>
  </div>
</div>

CSS Styling:

.box {
    width: 33%;
    border: solid 1px #ccc;
    margin: 1em;
  }

  .list {
    width: 100%;
    border: solid 1px #ccc;
    height: 90%;
    display: flex;
    flex-direction: row;
    background: white;
    border-radius: 4px;
    overflow: hidden;
    justify-content: space-around;
    flex-wrap: wrap;
  }

  .drag-placeholder {
    background: #ccc;
    width: 3em;
    border: dotted 3px #999;
    transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
  }

Component Logic:

export class AppComponent {

  timePeriods = ["1", "2", "3", "4", "5", "6", "7"];
  itemsTable: Array<string[]>;

  getItemsTable(rowLayout: Element): string[][] {
    if (this.itemsTable) {
      return this.itemsTable;
    }
    // Calculate column size per row
    const { width } = rowLayout.getBoundingClientRect();
    const boxWidth = Math.round(width * .33); // 33% as specified in css
    const columnSize = Math.round(width / boxWidth);

    // Determine row size: length of items / column size
    // Adding 0.5 ensures rounding up to display last element on new row
    const rowSize = Math.round(this.timePeriods.length / columnSize + .5);

    // Create table rows
    const copy = [...this.timePeriods];
    this.itemsTable = Array(rowSize)
      .fill("")
      .map(
        _ =>
          Array(columnSize) // fills until the end of column size, hence...
            .fill("")
            .map(_ => copy.shift())
            .filter(item => !!item) // ... removal of empty items is necessary
      );
    return this.itemsTable;
  }

  reorderDroppedItem(event: CdkDragDrop<number[]>) {
    // Clone table as it requires re-initialization after dropping
    let copyTableRows = this.itemsTable.map(_ => _.map(_ => _));

    // Handle item drop
    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
      );
    }

    // Update items post-drop
    this.timePeriods = this.itemsTable.reduce((previous, current) =>
      previous.concat(current)
    );

    // Re-initialize table
    let index = 0;
    this.itemsTable = copyTableRows.map(row =>
      row.map(_ => this.timePeriods[index++])
    );
  }

}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the method for extracting CSS class names and storing them in an array using PHP?

Hey there, I have a bunch of CSS code and I'm looking for a way to extract only the names of the CSS classes without the unnecessary characters and values, and then store them in an array using PHP. For Example: .dungarees { content: "\ef ...

Testing the Viewchild directive in Angular 2

I'm facing an issue with a component setup like this: export class ParentComponent implements OnInit, OnDestroy { @ViewChild(ChildComponent) childComponent: ChildComponent; } In this setup, the ParentComponent is using childComponent to make a ...

When I click on a tab section to expand it, the carat arrows all point upwards. Only the arrows corresponding to the selected section should

click here for imageIt appears that there are four tabs, each with a click function on the carat icon. When I expand one tab, all carats point upwards instead of only the selected one appearing. accountSelection(account) { if (!this.selectedAccoun ...

Comparing Angular 5 + Firebase Authentication: OnAuthStateChanged vs Token Verification

Whenever a user registers, the following code is executed: this.router.navigate(['/']); firebase.auth().currentUser.getToken() .then( (token: string) => this.token = token ) The getToken method is defined as follows: getToken() ...

Why won't the items budge from the expandable box?

I am facing an issue with dragging and dropping elements between two divs, where one is resizable. My goal is to be able to drag items back and forth between the two divs seamlessly. $(".item").draggable({ helper: 'clone' }); $( ".container" ). ...

Using `appendChild` in combination with `createElement`

Is there a difference between these two approaches: var div = document.createElement('div');//output -> [object HTMLDivElement] document.getElementById('container').appendChild(div); and: var div = '<div></div>&a ...

Adding a static global constant in webpack dynamically

I'm facing a challenge with adding a global constant to my project using webpack.DefinePlugin. I've successfully added one in the module.exports, but I struggle to do this conditionally. When I declare and use '__VERSION__' in my module ...

Chaining asynchronous HTTP requests in Angular 2: A guide to stopping execution if one request fails

I am faced with a challenge of executing an array of HTTP requests in a specific order, where if any request fails, the subsequent ones should not be executed. Is there a way to achieve this requirement? What would be the recommended approach to hand ...

Shift the checkbox label over to the left with just CSS

I am faced with the following code snippet: <div> <span> <input type="checkbox"> <label>I desire this to appear on the left-hand side</label> </span> </div> My goal is to shift the label to the left side of ...

Setting up Jest for an Angular projectorCustom

Setting up Jest for Angular seems straightforward according to different online guides. It involves the following steps: ng new jest-test cd jest-test npm i -D jest jest-preset-angular Make changes to package.json: "test": "jest", [...] "jest": { "p ...

Error in TypeScript: The object type '{ children: ReactNode; }' does not share any properties with type 'IntrinsicAttributes' in a dynamic React component

In the process of developing a React component using TypeScript, I aim to create a dynamic component that will encapsulate each child node passed to it. The component is named Slider and here's the functionality I envision: The component receives chi ...

When executing prisma generate, an error of TypeError is thrown stating that the collection is

While using typescript with Prisma, I encountered an issue when trying to run prisma generate, as it kept throwing the following error: TypeError: collection is not iterable. at keyBy (/node_modules/@prisma/client/generator-build/index.js:57685:21) at ...

How to halt the ng-serve process

Can you assist me in finding a script that can stop this issue on both Windows and Mac operating systems? ...

What methods are most effective for adjusting font size across mobile devices, desktop screens, and printed materials?

As I work on creating a website, I am faced with the challenge of making sure the font size is easily readable on mobile devices, desktop computers, and also when printed. I have noticed that the default font size on my desktop looks great, but is too sm ...

Tips for adding line breaks in TypeScript

I'm brand new to angular and I've been working on a random quote generator. After following a tutorial, everything seemed to be going smoothly until I wanted to add a line break between the quote and the author. Here's what I currently have ...

Creating a tabular layout using div elements and CSS styling

Here is the css and html code that I am working with: <style type="text/css"> .formLayout { background-color: #f3f3f3; border: solid 1px #a1a1a1; padding: 10px; width: 300px; border-radius: 1em; } ...

Angular: detecting mobile status within template

Often in my templates, I find myself repeating this type of code: <custom-button [align]="isMobile() ? 'center' : 'left'"></custom-button> This also requires me to include a method in each component to determine w ...

JQuery running into issues with proper appending

I am currently in the process of loading XML data using the JQuery.load function, and so far everything is going smoothly. The XML is successfully being loaded and inserted into the DOM, which is exactly what I intended. However, my next step involves iter ...

CSS causing unexpected behavior in image slider

I am facing an issue with incorporating a code for an image slideshow into my website. The current CSS is causing the slideshow to malfunction. Without the slideshow code in the CSS, the image appears correctly on the screen and changes its source every 2 ...

Tips for typing a JavaScript object in one line in TypeScript, with a variable name for the root property

I have this Javascript object: var termsAndConditions = { pt: ["url1", "url2"], en: ["url3", "url4"] } I am looking to elegantly convert it into Typescript in just one line. Here is what I came up with: const termsAndConditions: {[countryKey: Ar ...