Skip to content

bug(drag-drop): cdkDragPreview matchSize does not always match size due to view container insertion #19060

Closed
lingounet/testage
#29
@Achilles1515

Description

@Achilles1515

The below StackBlitz reproduction is a fork of the horizontal list official example. All that was added was a cdkDragPreview with the matchSize input property.

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

(In this case, the preview element is basically the same thing as the cdkDrag element, which doesn't make too much sense, but it is just for demonstration purposes and could contain different elements/styles)

Upon dragging an item, the preview does not match the same size as the original item/placeholder.

GIF
(Notice the "Bronze Age" preview is not as wide causing text wrap)

The cause of this problem is the flexbox droplist container (where the items grow/shrink to fill the available space) combined with how the preview element is created.

The preview template is currently being stamped out in the cdkDrag injected view container.

private _createPreviewElement(): HTMLElement {
    const previewConfig = this._previewTemplate;
    const previewClass = this.previewClass;
    const previewTemplate = previewConfig ? previewConfig.template : null;
    let preview: HTMLElement;

    if (previewTemplate) {
      // <-- HERE -->
      const viewRef = previewConfig!.viewContainer.createEmbeddedView(previewTemplate,
                                                                       previewConfig!.context);
      viewRef.detectChanges();
      preview = getRootNode(viewRef, this._document);
      this._previewRef = viewRef;

      if (previewConfig!.matchSize) {
        matchElementSize(preview, this._rootElement);
      } else {
        //...
      }
    } else {
      //...
    }
//...
}

If you put a breakpoint immediately after the viewRef.detectChanges() line, you will see the preview becoming another item in the list, as creating and inserting the embedded view in the view container essentially just tacks on another sibling.

image
("Bronze Age" was started to be dragged)

Since the preview item is added as a sibling, flexbox recalculates the width of all the items. The matchSize functionality is then executed with these modified (smaller, in this case) dimensions.


Possible Solution

Is a view container even necessary here?
Changing the above code to the following appears to work just fine.

 if (previewTemplate) {
      // <-- HERE -->
      //const viewRef = previewConfig!.viewContainer.createEmbeddedView(previewTemplate,
      //                                                                 previewConfig!.context);
      const viewRef = previewTemplate.createEmbeddedView(previewConfig!.context);
      viewRef.detectChanges();

This creates the same drag preview viewRef, except it is not immediately attached to the DOM.

The reproduction link contains some copied source code to preview this change - just need to comment/uncomment those lines.

Alternatively, the dragged item could be measured before the preview is created. But if there is no reason to use a view container, the above change seems like the more optimal approach.

Reproduction

StackBlitz

Expected Behavior

Preview to match size.

Actual Behavior

Preview does not match size.

Environment

  • Angular:
  • CDK/Material: 9.2
  • Browser(s):
  • Operating System (e.g. Windows, macOS, Ubuntu):

Metadata

Metadata

Assignees

Labels

P3An issue that is relevant to core functions, but does not impede progress. Important, but not urgent

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions