ngClass behaving differently than regular classes with cdk-drag-drop

I have different types of elements in my *cdkDropList that need different placholder hights when dragging them around. Since the array has objects with a property type, I added those types as CSS classes and tried to add them dynamically using [ngClass]. However, these dynamically generated classes behave differently than when I’m setting them as “regular” CSS classes.

This is what happens, when I set the classes dynamically:

The placeholder and the elements in the dropList overlap

The placeholder and the elements in the dropList overlap. Here’s the relevant code:

example.component.ts

contentItems: ContentItem[] = [
  { type: 'text', /* more props */ },
  { type: 'text', /* more props */ },
  { type: 'image', /* more props */ }
];

example.component.html

<div *ngFor="let item of contentItems" class="editor-item" cdkDrag>
  <div [ngClass]="['dropzone-placeholder', item.type]" *cdkDragPlaceholder>
    <p>{{ 'EDITOR.INSERT HERE' | translate }}</p>
  </div>

  <app-language-tab-editor *ngIf="item.type === 'text'"></app-language-tab-editor>
  <app-image-upload *ngIf="item.type === 'image'"></app-image-upload>
</div>

example.component.scss

$dropzone-placeholder-dark: #00973B;
$dropzone-placeholder-light: #00973B0D;
$text-placeholder-height: 135px;
$image-placeholder-height: 375px;

.dropzone-placeholder {
  border: 1px dashed $dropzone-placeholder-dark;
  color: $dropzone-placeholder-dark;
  background: $dropzone-placeholder-light;

  &.text {
    height: $text-placeholder-height;
  }

  &.image {
    height: $image-placeholder-height;
  }
}

I currently only have two different types, but the goal is to make it easily expandable to add more later on. I have also already tried to instead use class="dropzone-placeholder {{ item.type }}" as well as [class]="'dropzone-placeholder ' + item.type", to no avail.

After further testing I have also found out, that it generally doesn’t work using [ngClass], even if we don’t use a variable. Using [ngClass]="['dropzone-placeholder', 'text']" didn’t work either.

This is the expected behaviour:

The placeholder and the elements in the dropList don't overlap and are instead placed properly below each other

The placeholder and the elements in the dropList don’t overlap and are instead placed properly below each other. This behaviour can currently only be achieved by setting the classes regularily, but the HTML is rather unpleasant to look at, since the code would get messily redundant in the future:

example.component.html

<div *ngFor="let item of contentItems" class="editor-item" cdkDrag>
  <div *ngIf="item.type === 'text'">
    <div class="dropzone-placeholder reorder text" *cdkDragPlaceholder>
      <p>{{ 'EDITOR.INSERT HERE' | translate }}</p>
    </div>
  </div>
  <div *ngIf="item.type === 'image'">
    <div class="dropzone-placeholder reorder image" *cdkDragPlaceholder>
      <p>{{ 'EDITOR.INSERT HERE' | translate }}</p>
    </div>
  </div>

  <app-language-tab-editor *ngIf="item.type === 'text'"></app-language-tab-editor>
  <app-image-upload *ngIf="item.type === 'image'"></app-image-upload>
</div>

Environment

  • Angular: v8.3.25
  • CDK: v8.2.3
  • Browser(s): Chrome, Firefox
  • Operating System: Windows 10 (1803)

1 possible answer(s) on “ngClass behaving differently than regular classes with cdk-drag-drop

  1. @mladenbrankovic The problem is because you are specifying height values in your dynamic classes that change the div geometry…but these dynamic classes only get added after change detection runs on the template. I don’t know all the details, but I think the CDK code starts “using” this placeholder template before its first change detection run.

    See this StackBlitz.

    This demo includes a monkey patched _createPlaceholderElement() method. The only new code line is the one outlined in comments, manually triggering change detection on the dynamically instantiated template. Adding this line makes the placeholder work properly – comment it out to see otherwise.

    It is still somewhat hard to see exactly what is going on with your drag setup based on those two images, but I think the above should fix it. I would try adding that monkey patch code to your project and see if it fixes the problem.

    @crisbeto What are your thoughts on adding the detectChanges() line to _createPlaceholderElement() as well as _createPreviewElement() immediately after dynamic template instantiation?