Tips for maintaining the order of child elements in Angular when deleting one of them

I have a parent Angular component that showcases multiple children components using the ngFor directive. Each child functions as its own window within the parent container and can be repositioned using CdkDrag. Additionally, I have added a small "X" button in the top right corner of each child component to allow for closing. However, when I close a child with a lower index (such as 1 or 2), the remaining children are automatically rearranged. Is there a way to prevent this rearrangement and keep the layout consistent when closing any child window?

Child Component

@Input('target') target: string = '';
@Input('index') index: string = '';
@Output() onClose: EventEmitter<number> = new EventEmitter();

closeModal() {
  const i: number = +this.index;
  this.onClose.emit(i);
}

Child Template

<div class="example-box" cdkDrag>
  {{target}}
  <button class="CloseButton" (click)="closeModal()">X</button>
</div>

Child CSS

.example-box {
  width: 100px;
  height: 100px;
  border: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  justify-content: center;
  position: relative;
  resize: both;
}

.CloseButton {
  position: absolute;
  top: 10px;
  right: 10px;
}

Parent Component

  names: string[] = ['1', '2', '3'];
  modalClosed(id: any) {
    this.names.splice(id, 1);
    console.log(id);
  }

Parent Template

<div class="ParentMain">
  <child-comp
    *ngFor="let name of names ; index as i"
    (onClose)="modalClosed($event)"
    target="{{name}}"
    index="{{i}}"
  >
  </child-comp>
</div>

Parent CSS

.ParentMain {
  display: flex;
}

Complete StackBlitz Example

Link to StackBlitz example code

Answer №1

Another method worth considering is the one I recall being used in this Stack Overflow post

If we picture a cdkDropList with "items" inside, we can implement something like this:

<div
  cdkDropList
  #doneList="cdkDropList"
  [cdkDropListData]="done"
  class="drag-zone"
  cdkDropListSortingDisabled="true"
>
  <div
    *ngFor="let item of done; let i=index"
    cdkDrag
    class="item-box"
    [style.top.px]="item.y"
    [style.left.px]="item.x"
    [style.z-index]="item['z-index']"
    (cdkDragStarted)="changeZIndex(item)"
    (cdkDragDropped)="changePosition($event, item)"
  >
    <child-comp
      class="item-box"
      [target]="item.name"
      [index]="i"
      (onClose)="modalClosed($event)"
    >
    </child-comp>
    <div *cdkDragPlaceholder class="field-placeholder"></div>
  </div>
</div>

Remember, a cdkDropList is not the same as a regular list!

names: string[] = ['1', '2', '3'];
done = this.names.map((x, index) => ({name: x, x: index*100, y: 0, "z-index": 0}));

modalClosed(id: any) {
  this.done.splice(id, 1);
  console.log(id);
}

@ViewChild('doneList', { read: ElementRef, static: true }) dropZone: ElementRef;

changeZIndex(item: any) {
  this.done.forEach((x) => (x['z-index'] = x == item ? 1 : 0));
}
changePosition(event: CdkDragDrop<any>, field: any) {
  const rectZone = this.dropZone.nativeElement.getBoundingClientRect();
  const rectElement = event.item.element.nativeElement.getBoundingClientRect();

  let y = +field.y + event.distance.y;
  let x = +field.x + event.distance.x;
  field.y = y;
  field.x = x;
  this.done = this.done.sort((a, b) =>
    a['z-index'] > b['z-index'] ? 1 : a['z-index'] < b['z-index'] ? -1 : 0
  );
}

Here are some CSS styles to be aware of:

.drag-zone{
  position: relative;
  flex-grow: 1;
  height: 20rem;
  border: 1px solid silver;
  overflow: hidden;
}
.item-box {
  position: absolute;
}

.cdk-drag-preview child-comp {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
              0 8px 10px 1px rgba(0, 0, 0, 0.14),
              0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.drag-zone .cdk-drag-placeholder {
  opacity: 0;
}

Check out the working example on Stackblitz

Answer №2

When using cdk drag functionality, the style of the element can be changed by adding a transform:translate3D. This can cause elements to reposition if the "origin" is altered (for example, if the elements are contained in a flex div).

To prevent this issue, you can set the position to absolute and specify top and left coordinates using the event endDrag. Instead of applying the "cdkDrag" directly to the element, consider wrapping it in a child div for easier dragging.

<!-- Make sure to use the template reference variable "wrapper" -->
<div #wrapper style="position:relative;display:flex">

  <!-- Pass the component during cdkDragStarted event -->
  <child-comp #el cdkDrag (cdkDragStarted)="dragStart(el)" 
                          (cdkDragEnded)="dragEnd($event, i)" 
                          (onClose)="modalClosed($event)"
    *ngFor="let name of names; index as i"
    target="{{name}}"
    index="{{i}}"
  >
  </child-comp>
</div>

In your child component, access the ElementRef by injecting it into the constructor:

// Inside child component
constructor(public elementRef:ElementRef) {}

During dragEnd event:

dragEnd(event: CdkDragEnd)
{
    // Retrieve the element:
    const el = event.source.element.nativeElement

    // Change the style to position absolute
    el.style.position='absolute'
    el.style.transform=''
    el.style.top=(this.oldPosition.y + event.distance.y)+'px'
    el.style.left=(this.oldPosition.x + event.distance.x)+'px'
}

If you are using an older version of cdk-drag that does not provide the "position" property in the event, modifications are needed when startDrag is triggered:

dragStart(component:any)
{
    const posWrapper=this.container.nativeElement.getBoundingClientRect()
    const pos=component.elementRef.nativeElement.getBoundingClientRect()
    this.oldPosition={x:pos.x-posWrapper.x,y:pos.y-posWrapper.y}
    const el=component.elementRef.nativeElement;
    el.style.top=''
    el.style.left=''
    el.style.position='relative'

}

Lastly, ensure that the "wrapper" retains its width and height after all elements have been dragged:

ngAfterViewInit()
{
    const posWrapper=this.container.nativeElement.getBoundingClientRect()
    this.container.nativeElement.style.width=posWrapper.width+'px'
    this.container.nativeElement.style.height=posWrapper.height+'px'
}

Check out the StackBlitz for a working example.

Answer №3

One important aspect to consider is how you manage the positioning of items during drag and drop interactions. It is crucial to save the item's position after the onDragEnded event. For a demonstration, you can refer to this example here:

Answer №4

To maintain the location of each conversation after closing them with a close button, there are several methods you can use. One straightforward approach is to simply hide the dialogues when the button is clicked instead of completely removing them from the layout.

For instance, here's an example demonstrating this concept: https://stackblitz.com/edit/angular-pqf4je-aatkjg?file=src%2Fapp%2Fchild-comp.html

In this scenario, clicking on a dialogue makes it disappear while ensuring that the other items remain in place since the item still exists but has been set to opacity: 0.

Alternatively, a more sophisticated method involves using CSS to position the items in specific locations so that they do not shift when one is removed, avoiding the issue of numerous invisible components cluttering the screen.

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

Adjusting an image size using JQuery after resizing a canvas will only resize the image itself, not

How can I resize an image within a canvas using JQuery in a way that only the image is resized, not the entire canvas? function resizeImage(width, height){ var image = document.getElementById('resizeImage'), canvas = document.createEleme ...

Loop through a collection of objects in Angular 2

Upon subscribing to an array of objects received from a JSON file in the service file, I encountered an error while trying to iterate through it. The error message I received was: EXCEPTION: Error in app/dashboard/features/fleet/fleetControlPanel/fleetCon ...

Creating a visually appealing line design with customizable column widths using CSS

Currently, I am working on designing a user registration page for a testing application and facing some challenges with the layout. CSS has never been my strong suit, but I can manage to style most of the elements except for one - the birthday field. My fo ...

Every time I attempt to visit my website on a mobile device, it seems to appear larger than normal, as

After developing a responsive website and adding media queries to make it mobile-friendly, I encountered an issue. Despite my efforts, the website's width is still larger than the mobile viewport, requiring users to zoom out to view it properly as sho ...

Utilize Primeng form to link input values to two separate model parameters

Currently, my primeng form is responsible for updating the user model upon submission. However, I now have a requirement to update another model simultaneously. Essentially, I need to extract the username input from the form and use it to create an object ...

The jQuery window.load() function fails to execute on iOS5 devices

I added a basic loading div to my website that fades out once the entire page has finished loading. The code I used is simple: $(window).load(function(){ $('#loading').fadeOut(500); }); While this works well on all desktop browsers and Chro ...

Setting up a reverse proxy in Angular 2: A step-by-step guide

My project setup includes: "start": "gulp serve.dev --color", In my service class, I have the following code snippet: this.mapUrl = @apiproxy return this.http.post(this.mapUrl, body, { headers: this.headers }) The Proxy.config.json file contains: { "/ ...

Place images with variable height inside floated divs, specifying their height in percentages

Is there a simple way to make images fill 100% of the height of a div? If you want to place images inside divs where the images fill 100% of the height of the div, use the following CSS: .container{ height:100%; float:left; } img { hei ...

Is there an easy method to compare the CSS styles of a specific section in HTML?

Currently, I am facing an issue where the size of a specific area on my asp.net page changes after a post-back. It seems that the style applied to this area is being altered for some reason. This situation has raised the question in my mind - is there a m ...

The full-page layout features a convenient scroll bar situated at the bottom for easy

I've been grappling with a perplexing CSS issue that is causing a scroll bar to appear on a full page. Initially, my div layout was as follows (with no scrollbar problem): Container 100% (width) wrapper 80% navMenu 100% centerDoc 100% Ho ...

Utilizing a compiled Electron application built with Angular and ready to launch

I am facing an issue with my Electron app that is built using Angular. Everything works perfectly until I package the app as an installable one with electron-builder. After installation, when I run the app, it opens up with a blank screen. Upon checking th ...

I'm looking to increase a specific value by a set amount each time there is an input event triggered

I have created a range slider, but I am facing an issue with the oninput event. I want to increment a specific amount whenever the slider is adjusted. For example, I want to increase the amount by $150 when the slider is at $2000, and by $45000 for a parti ...

Show concealed content for individuals who do not have javascript enabled

One of the challenges I faced was creating a form with a hidden div section that only appears when a specific element is selected from a list. To achieve this, I utilized CSS to set the display property of the div to 'none' and then used jQuery t ...

What are the steps to generate a production build directory in a React project?

Currently, I am developing a website utilizing react for the frontend and node.js for the backend. However, I'm unsure of how to deploy it to the live server. After conducting some research, I learned that I need to create a build folder. To provide a ...

Managing errors with the RxJS retry operator

I'm facing an issue with my RxJS code where I need to continuously retry a data request upon failure while also handling the error. Currently, I am using the retry operator for this purpose. However, when attempting to subscribe to the retry operator ...

Animating shifting of an overflowing div using jQuery

Struggling to articulate this, so here is a little jsfiddle demo: http://jsfiddle.net/UwEe2/ The concept I am aiming for is very similar to the one in the demo, however, I require the image to be perfectly centered. In other words, I need the center of t ...

How to turn off spin buttons for input type=“number” in AngularJS when using webkit

Although similar advice can be found for 'normal' Bootstrap, the solution may not be the same for AngularJS. input[type=number]::-webkit-inner-spin-button, input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: ...

How can a callback function be connected to a specific route within an Angular routing module?

Currently, I am in the process of developing a robust Angular application that utilizes feature modules with child routes. Each child section comes with its own specific layout, which establishes the state for the child pages, especially for the Minimum Vi ...

Changing the parent div element based on the child div element

Is there a way to dynamically apply style properties, such as 'float', to the parent div based on its child div element, preferably using JavaScript without the need for additional div elements? <div class="chat-message"> <span clas ...

Adding a caption aligned to the right edge of an image in Wordpress

Any suggestions on aligning an image caption under an image tag to its right hand edge? I attempted using a div, but it seems it's not allowed in WordPress. Can anyone recommend alternative CSS or tags that I could use for this purpose? ...