1

To work around a problem with dragula I thought I add and remove the elements from the DOM instead. The goal is to use some placeholder elements to get the layout I want (left aligned elements in a centered container at all resolutions). These placeholder elements have to be placed at the end!

I tried the following approaches:

  • ngIf: problem here is that it can happen that the invisible elements are not placed at the end. Through drag & drop it can happen that the dropped element is on the last place and the invisible elements are between.
  • push elements on the array: here I tried to add the class invisible with the help of the ngClass directive, but it didn't update it correctly, when I set my boolean variable while pushing items on the array. The class was never applied.
  • ElementRef: removing an element from the DOM worked, but I didn't figured out how to add one
  • Renderer2: my current approach, where removeChild() only removes one child despite it is called a second time

Template:

<div #button_area class="button_area" dragula="cards" [dragulaModel]="cards" (dragulaModelChange)="cards = $event">
  <div *ngFor="let c of cards" id="{{ 'card-' + c.id }}" [ngClass]="calculateClasses(c)" [ngStyle]="calculateStyles(c)" (click)="onSelect($event, c)">
    <!-- some irrelevant html code -->
  </div>
</div>

Typescript:

@ViewChild('button_area') private buttonAreaElement: ElementRef;

constructor(public dragulaService: DragulaService,
          private renderer: Renderer2) {
    // something irrelevant
}

addPlaceholderElements() {
    const placeholder = this.buttonAreaElement.nativeElement.querySelector('.invisible');

    if (placeholder === null) {
        const placeholder1 = this.renderer.createElement('div');
        this.renderer.addClass(placeholder1, 'card');
        this.renderer.addClass(placeholder1, 'invisible');
        this.renderer.appendChild(this.buttonAreaElement.nativeElement, placeholder1);

        const placeholder2 = this.renderer.createElement('div');
        this.renderer.addClass(placeholder2, 'card');
        this.renderer.addClass(placeholder2, 'invisible');
        this.renderer.appendChild(this.buttonAreaElement.nativeElement, placeholder2);
    }
}

removePlaceholderElements() {
    const placeholder1 = this.buttonAreaElement.nativeElement.querySelector('.invisible');
    if (placeholder1 !== null) {
        this.renderer.removeChild(this.buttonAreaElement.nativeElement, placeholder1);
    }

    const placeholder2 = this.buttonAreaElement.nativeElement.querySelector('.invisible');
    if (placeholder2 !== null) {
        this.renderer.removeChild(this.buttonAreaElement.nativeElement, placeholder2);
    }
}

As you can see I use the querySelector() method for this. placeholder2 is found, but not removed from the DOM. But why?

I'm using Angular v. 7.2.5.

Edit:

Here is the MVCE. There the issue with the deletion doesn't occur. Haven't figured out what the difference is ... Why doesn't removeChild() remove the child? Is this a timing issue?

testing
  • 19,681
  • 50
  • 236
  • 417
  • 1
    Modifying the DOM yourself is usually a bad idea. Especially with Angular libraries such as Dragula. Please provide a [mcve] of your issue, and I'll see how I can work around your problem ! –  Jun 05 '19 at 11:38
  • @Maryannah: I have added a stackblitz project (see edited question). Perhaps you have a better idea how to solve this. – testing Jun 05 '19 at 13:27
  • 1
    Had an issue or two with dragula as well - why don't you use native ng-7 `CdkDragDrop, moveItemInArray, transferArrayItem` from `@angular/cdk/drag-drop` instead of dragula ? – iLuvLogix Jun 05 '19 at 13:33
  • @iLuvLogix I love that suggestion. Dragula is old and way less updated than the CDK ! As for the OP, I'll look at it right now and keep you updated –  Jun 05 '19 at 13:35
  • OP, what's your issue exactly ? What are the steps to reproduce ? –  Jun 05 '19 at 13:36
  • @iLuvLogix: First of all the real underlying issue is a layout issue (because I need the placeholder elements for centering). Second, cdk has problems with multiple lines and horizontal direction in the flexbox (see [here](https://github.com/angular/components/issues/13372)). And it seems that it isn't get fixed. – testing Jun 05 '19 at 13:37
  • @Maryannah: To get the right layout I use placeholder elements. When I use the placeholder elements these are possible locations for `dragula`. I don't want them as drop location. Because I can't exclude these placeholder elements I thought I add and remove them as necessary. In my last attempt I use `Renderer2`. In my real project `removeChild` doesn't remove the child and one (of the two) placeholder element stays. In the MVCE this isn't the case, but I don't know why. – testing Jun 05 '19 at 13:41
  • @Maryannah: Steps to reproduce in my real project: 1. Enable dragging (click on enable) 2. Change location of an element by drag & drop 3. Disable dragging 4. Enable dragging again. Here there is the placeholder element, which shouldn't be there. – testing Jun 05 '19 at 13:45

1 Answers1

1

To answer my own question:

As shown in the question, there are multiple ways to add or remove elements (e.g. via Renderer2). In general, it is not advisable to manipulate the DOM directly as Maryannah stated. But sometimes it is the last resort.

To get the right layout I use placeholder elements. When I use the placeholder elements these are possible locations for dragula. I don't want them as drop location. Because I can't exclude these placeholder elements I thought I add and remove them as necessary. In my last attempt I use Renderer2. In my real project removeChild doesn't remove the child and one (of the two) placeholder element stays. In the MVCE this isn't the case, but I don't know why.

What was missing for my workaround of the workaround is the complete removal of all childs with the CSS class invisible. This now does also work in my local development environment:

removePlaceholderElements() {
    const placeholders = this.buttonAreaElement.nativeElement.querySelectorAll('.invisible');
    for (var i = 0; i < placeholders.length; i++) {
        this.renderer.removeChild(this.buttonAreaElement.nativeElement, placeholders[i]);
    }
}
testing
  • 19,681
  • 50
  • 236
  • 417