Easily place unique static components by utilizing Angular Material CDK's drag and drop feature

When working with Angular CDK Drag & Drop, I am building a straightforward dashboard that includes a left sidebar and main content area. Both sections will contain unique custom components that should be draggable, reorderable within their respective areas, and transferable to other areas.

For example, if the Sidebar contains Comp and Comp1, I want to be able to rearrange them within the Sidebar and move them to the Main Content area.

As far as I know, Angular Material CDK Drag & Drop is designed for lists and requires items in those lists to be of the same type in order to be reordered or transferred.

Is there a way to use CDKDrag and CDKDropList for static items rather than items stored in an array? I am facing issues trying to reorder or transfer custom components to different drop zones.

I have prepared a sample project: https://stackblitz.com/edit/ng-mat-dnd Demo:

app.component.html

<div class="example-container">
    <h2>Sidebar</h2>

    <div cdkDropList #sidebarList="cdkDropList" [cdkDropListData]="sidebar" cdkDropListConnectedTo="[mainList]"
        class="example-list" (cdkDropListDropped)="drop($event)">
        <div class="example-box" cdkDrag>
            <app-demo-comp-2 [btn]=2></app-demo-comp-2>
        </div>
        <div class="example-box" cdkDrag>
            <app-demo-comp [ddn]=2></app-demo-comp>
        </div>
        <div class="example-box" cdkDrag>
            <app-demo-comp-3 [txt]=3></app-demo-comp-3>
        </div>
    </div>
</div>

<div class="example-container">
    <h2>Main</h2>

    <div cdkDropList #mainList="cdkDropList" [cdkDropListData]="main" cdkDropListConnectedTo="[sidebarList]"
        class="example-list" (cdkDropListDropped)="drop($event)">
        <div class="example-box" cdkDrag>
            <app-demo-comp [ddn]=1></app-demo-comp>
        </div>
        <div class="example-box" cdkDrag>
            <app-demo-comp-2 [btn]=3></app-demo-comp-2>
        </div>
    </div>
</div>

app.component.ts

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

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {  
  sidebar;
  main;

  @ViewChildren(CdkDrag) draggables: QueryList<CdkDrag>;

  constructor() { }

  ngOnInit() { }

  ngAfterViewInit() {
    console.log(this);
    this.sidebar = [this.draggables.toArray()[0], this.draggables.toArray()[1], this.draggables.toArray()[2]];
    console.log(this.sidebar);
    
    this.main = [this.draggables.toArray()[4], this.draggables.toArray()[3]];
    console.log(this.main);
  }

  drop(event: CdkDragDrop<any[]>) {
    console.log(event);
    if (event.previousContainer === event.container) {
      console.log('Same container');
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      console.log('Different containers');
      transferArrayItem(event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex);
    }
  }
}

Answer №1

When utilizing Angular Material's cdk drag and drop feature, it is crucial that the element retains its visibility and is placed back at its previous position in the DOM.

The reason for this emphasis on maintaining position is due to the reliance on the ngFor directive, as rearranging elements within the DOM can disrupt NgFor's ability to intelligently diff and only recreate elements when necessary.

This implies that if you wish to avoid using ngFor, you will need to manually adjust the positioning of HTML elements.

Below is an example demonstrating how this manual manipulation can be achieved:

Stackblitz showcasing manual DOM manipulations

drop(event: CdkDragDrop<any[]>) {
  // Implementation details
}

...

// Additional helper functions for moving items within container and transferring nodes
function moveWithinContainer(container, fromIndex, toIndex) {
  // Implementation details
}

function transferNodeToContainer(node, container, toIndex) {
  // Implementation details
}

Utilizing NgFor Directive

The previously mentioned solution may seem a bit hacky and might not always be reliable depending on library versions.

An alternative approach is to leverage the ngFor directive instead:

ts

sidebar = [0, 1, 2];
main = [0, 1];

html

 <div *ngFor="let item of sidebar" class="example-box" cdkDrag>
    <!-- Implementation details -->
 </div> 

 ...
 <div *ngFor="let item of main" class="example-box" cdkDrag>
    <!-- Implementation details -->
 </div>

Stackblitz highlighting NgFor usage

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

Steps for navigating to an element on a page upon clicking a link within a distinct Angular 7 mat-toolbar component

I am currently working on an Angular 7 project that features a mat-toolbar at the top of the page with links, followed by a long view consisting of different sections. Each section is built as its own component and nested within a main component called Hom ...

`The term 'promise' is typically used to describe a type, yet in this context, it is being utilized as a value.`

I attempted to employ a promise for an Async call in my custom form validator, so I created a separate TypeScript file called usernameValidators.ts. import { Control } from 'angular2/common'; export class UsernameValidators { static should ...

Utilize ngModelGroup to avoid duplicating the div element

Here is an example of a form layout: <input type="checkbox"> <input type="text"><br> <input type="checkbox"> <input type="text"><br> <input type="checkbox"> <input type="text"> All text fields belong to t ...

What is the most efficient method for sharing types within an extensive TypeScript project?

I'm currently developing a complex React application using TypeScript. I have numerous common types defined in separate files, and I find myself importing them every time I need to use them. While this approach is functional, it results in a large num ...

What categories do input events fall into within Vue?

What Typescript types should be used for input events in Vue to avoid missing target value, key, or files properties when using Event? For example: <input @input="(e: MISSING_TYPE) => {}" /> <input @keypress="(e: MISSING_TYPE) = ...

Challenges with Ionic 4 - Module '@angular/core/package.json' not found

Having some trouble with an Ionic 4 project code that works perfectly on my Mac but encounters an error when I try to run it on my Windows 10 PC. The specific error message states "Cannot find module '@angular/core/package.json'". Interestingly, ...

Steps for eliminating the chat value from an array tab in React

tabs: [ '/home', '/about', '/chat' ]; <ResponsiveNav ppearance="subtle" justified removable moreText={<Icon icon="more" />} moreProps={{ noCar ...

typescript exploring the versatility of dynamic types and generics

Understanding TypeScript dynamic and generic types can be challenging for me. The goal is to create a function that generates an object with a specific type, where some properties of the object must match the parameters provided to the function. Essentia ...

Encountering Compilation Error When Using RxJS Observable with Angular 6 and Swagger Codegen

Encountering TypeScript compiler errors related to rxjs while working with Angular 6 and Swagger Codegen: Cannot find module 'rxjs-compat/Observable' Referenced the following link for assistance: https://github.com/ReactiveX/rxjs/blob/master/M ...

Error: The method "next" cannot be used with BehaviorSubject

My goal is to share data between sibling components by utilizing a shared service. The first component loads and retrieves a list of Servers from the API, populating a select box with all the servers. I now need to notify the other component when the user ...

What's the best way to display a component once a function has been executed?

My service controls the visibility of components using *ngIf! Currently, when I click a button, the service sets to true and the components appear instantly! I want the components to only show after a specific function has finished executing. This means I ...

How can I assign a specific class to certain elements within an *ngFor loop in Angular?

I have a situation where I am utilizing the *ngFor directive to display table data with the help of *ngFor="let record of records". In this scenario, I am looking to assign a custom CSS class to the 'record' based on specific conditions; for exam ...

Angular - What causes variables to lose data after component changes?

Having an issue with two components - one creating and changing an array, and the other getting the array. The problem is that when retrieving the array in the second component, it only shows the default empty data. Code for array creation: export cla ...

The change handler of the TS RadioGroup component, instead of returning an array of possible strings, returns a unique

My interface declaration for StopData is shown below: export interface StopData { stopName: string, stopType: 'stop' | 'waypoint' } I have implemented a radio group to choose the 'stopType', which consists of two radi ...

Issue: Unable to find solutions for all parameters in (?, ?)

Below is the unit test I've written for my Angular 10 component, which showcases a tree view with interactive features: import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/for ...

Ensuring that an object containing optional values meets the condition of having at least one property using Zod validation

When using the Zod library in TypeScript to validate an object with optional properties, it is essential for me to ensure that the object contains at least one property. My goal is to validate the object's structure and confirm that it adheres to a sp ...

Tips on eliminating expansion upon button click in header within an Angular application

While utilizing Angular Materials, I encountered a challenge with the mat-expansion component. Every time I click on the buttons within the expansion panel, it closes due to the default behavior of mat-panel. Requirement - The panel should remain expanded ...

Ensure that type checking is applied exclusively to the properties of an object, and define the keys as constants

Defining constants with predefined keys in typescript can be confusing at times. Let's walk through an example: // suppose we have this object const MY_LIB = { id_1: {foo: 'foo', bar: 'bar'}, id_2: {foo: 'foo', ba ...

The type '(dispatch: Dispatch<any>, ownProps: OwnProps) => DispatchProps' does not match the parameter type 'DispatchProps'

Currently, I am working on a React application using Redux and TypeScript. I came across this insightful article that provided guidance on creating types for the mapStateToProps and mapDispatchToProps functions. Below is the code for my container: import ...

Dealing with Angular Unit Tests can be a hassle: they don't uncover issues until it's too late in the build or compile

It seems like every time I write tests for Angular, they fail at runtime instead of build time. Issues like 'No Provider found', 'Null', and 'etc NULL' keep popping up, and I end up constantly searching on Google for solutions ...