diff --git a/src/cdk/drag-drop/directives/drag.spec.ts b/src/cdk/drag-drop/directives/drag.spec.ts
index 0b8c79b0e907..a80f1f05c063 100644
--- a/src/cdk/drag-drop/directives/drag.spec.ts
+++ b/src/cdk/drag-drop/directives/drag.spec.ts
@@ -5592,6 +5592,21 @@ describe('CdkDrag', () => {
expect(event.stopPropagation).toHaveBeenCalled();
}));
+
+ it('should stop event propagation when dragging item nested via ng-template', fakeAsync(() => {
+ const fixture = createComponent(NestedDragsThroughTemplate);
+ fixture.detectChanges();
+ const dragElement = fixture.componentInstance.item.nativeElement;
+
+ const event = createMouseEvent('mousedown');
+ spyOn(event, 'stopPropagation').and.callThrough();
+
+ dispatchEvent(dragElement, event);
+ fixture.detectChanges();
+
+ expect(event.stopPropagation).toHaveBeenCalled();
+ }));
+
});
});
@@ -6557,6 +6572,52 @@ class NestedDragsComponent {
itemDragReleasedSpy = jasmine.createSpy('item drag released spy');
}
+@Component({
+ styles: [`
+ :host {
+ height: 400px;
+ width: 400px;
+ position: absolute;
+ }
+ .container {
+ height: 200px;
+ width: 200px;
+ position: absolute;
+ }
+ .item {
+ height: 50px;
+ width: 50px;
+ position: absolute;
+ }
+ `],
+ template: `
+
+
+
+
+
+
+
+
+ `
+})
+class NestedDragsThroughTemplate {
+ @ViewChild('container') container: ElementRef;
+ @ViewChild('item') item: ElementRef;
+}
+
@Component({
styles: [`
.drop-list {
diff --git a/src/cdk/drag-drop/directives/drag.ts b/src/cdk/drag-drop/directives/drag.ts
index 312a09edfb40..b14ae7c1db5f 100644
--- a/src/cdk/drag-drop/directives/drag.ts
+++ b/src/cdk/drag-drop/directives/drag.ts
@@ -56,12 +56,14 @@ import {DragDrop} from '../drag-drop';
import {CDK_DRAG_CONFIG, DragDropConfig, DragStartDelay, DragAxis} from './config';
import {assertElementNode} from './assertions';
+const DRAG_HOST_CLASS = 'cdk-drag';
+
/** Element that can be moved inside a CdkDropList container. */
@Directive({
selector: '[cdkDrag]',
exportAs: 'cdkDrag',
host: {
- 'class': 'cdk-drag',
+ 'class': DRAG_HOST_CLASS,
'[class.cdk-drag-disabled]': 'disabled',
'[class.cdk-drag-dragging]': '_dragRef.isDragging()',
},
@@ -69,6 +71,7 @@ import {assertElementNode} from './assertions';
})
export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy {
private _destroyed = new Subject();
+ private static _dragInstances: CdkDrag[] = [];
/** Reference to the underlying drag instance. */
_dragRef: DragRef>;
@@ -193,17 +196,21 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy {
@Optional() private _dir: Directionality, dragDrop: DragDrop,
private _changeDetectorRef: ChangeDetectorRef,
@Optional() @Self() @Inject(CDK_DRAG_HANDLE) private _selfHandle?: CdkDragHandle,
- @Optional() @SkipSelf() @Inject(CDK_DRAG_PARENT) parentDrag?: CdkDrag) {
+ @Optional() @SkipSelf() @Inject(CDK_DRAG_PARENT) private _parentDrag?: CdkDrag) {
this._dragRef = dragDrop.createDrag(element, {
dragStartThreshold: config && config.dragStartThreshold != null ?
config.dragStartThreshold : 5,
pointerDirectionChangeThreshold: config && config.pointerDirectionChangeThreshold != null ?
config.pointerDirectionChangeThreshold : 5,
zIndex: config?.zIndex,
- parentDragRef: parentDrag?._dragRef
});
this._dragRef.data = this;
+ // We have to keep track of the drag instances in order to be able to match an element to
+ // a drag instance. We can't go through the global registry of `DragRef`, because the root
+ // element could be different.
+ CdkDrag._dragInstances.push(this);
+
if (config) {
this._assignDefaults(config);
}
@@ -318,6 +325,10 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy {
this.dropContainer.removeItem(this);
}
+ const index = CdkDrag._dragInstances.indexOf(this);
+ if (index > -1) {
+ CdkDrag._dragInstances.splice(index, -1);
+ }
this._destroyed.next();
this._destroyed.complete();
this._dragRef.dispose();
@@ -392,6 +403,29 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy {
}
}
});
+
+ // This only needs to be resolved once.
+ ref.beforeStarted.pipe(take(1)).subscribe(() => {
+ // If we managed to resolve a parent through DI, use it.
+ if (this._parentDrag) {
+ ref.withParent(this._parentDrag._dragRef);
+ return;
+ }
+
+ // Otherwise fall back to resolving the parent by looking up the DOM. This can happen if
+ // the item was projected into another item by something like `ngTemplateOutlet`.
+ let parent = this.element.nativeElement.parentElement;
+ while (parent) {
+ // `classList` needs to be null checked, because IE doesn't have it on some elements.
+ if (parent.classList?.contains(DRAG_HOST_CLASS)) {
+ ref.withParent(CdkDrag._dragInstances.find(drag => {
+ return drag.element.nativeElement === parent;
+ })?._dragRef || null);
+ break;
+ }
+ parent = parent.parentElement;
+ }
+ });
}
/** Handles the events from the underlying `DragRef`. */
diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts
index d0340a3567de..1784b1e546f5 100644
--- a/src/cdk/drag-drop/drag-ref.ts
+++ b/src/cdk/drag-drop/drag-ref.ts
@@ -230,6 +230,9 @@ export class DragRef {
/** Layout direction of the item. */
private _direction: Direction = 'ltr';
+ /** Ref that the current drag item is nested in. */
+ private _parentDragRef: DragRef | null;
+
/**
* Cached shadow root that the element is placed in. `null` means that the element isn't in
* the shadow DOM and `undefined` means that it hasn't been resolved yet. Should be read via
@@ -324,7 +327,7 @@ export class DragRef {
private _viewportRuler: ViewportRuler,
private _dragDropRegistry: DragDropRegistry) {
- this.withRootElement(element);
+ this.withRootElement(element).withParent(_config.parentDragRef || null);
this._parentPositions = new ParentPositionTracker(_document, _viewportRuler);
_dragDropRegistry.registerDragItem(this);
}
@@ -430,6 +433,12 @@ export class DragRef {
return this;
}
+ /** Sets the parent ref that the ref is nested in. */
+ withParent(parent: DragRef | null): this {
+ this._parentDragRef = parent;
+ return this;
+ }
+
/** Removes the dragging functionality from the DOM element. */
dispose() {
this._removeRootElementListeners(this._rootElement);
@@ -461,7 +470,7 @@ export class DragRef {
this._resizeSubscription.unsubscribe();
this._parentPositions.clear();
this._boundaryElement = this._rootElement = this._ownerSVGElement = this._placeholderTemplate =
- this._previewTemplate = this._anchor = null!;
+ this._previewTemplate = this._anchor = this._parentDragRef = null!;
}
/** Checks whether the element is currently being dragged. */
@@ -790,7 +799,7 @@ export class DragRef {
private _initializeDragSequence(referenceElement: HTMLElement, event: MouseEvent | TouchEvent) {
// Stop propagation if the item is inside another
// draggable so we don't start multiple drag sequences.
- if (this._config.parentDragRef) {
+ if (this._parentDragRef) {
event.stopPropagation();
}
diff --git a/tools/public_api_guard/cdk/drag-drop.d.ts b/tools/public_api_guard/cdk/drag-drop.d.ts
index fc5588fd0f21..cfc4447935d8 100644
--- a/tools/public_api_guard/cdk/drag-drop.d.ts
+++ b/tools/public_api_guard/cdk/drag-drop.d.ts
@@ -42,7 +42,7 @@ export declare class CdkDrag implements AfterViewInit, OnChanges, OnDes
constructor(
element: ElementRef,
dropContainer: CdkDropList,
- _document: any, _ngZone: NgZone, _viewContainerRef: ViewContainerRef, config: DragDropConfig, _dir: Directionality, dragDrop: DragDrop, _changeDetectorRef: ChangeDetectorRef, _selfHandle?: CdkDragHandle | undefined, parentDrag?: CdkDrag);
+ _document: any, _ngZone: NgZone, _viewContainerRef: ViewContainerRef, config: DragDropConfig, _dir: Directionality, dragDrop: DragDrop, _changeDetectorRef: ChangeDetectorRef, _selfHandle?: CdkDragHandle | undefined, _parentDrag?: CdkDrag | undefined);
getFreeDragPosition(): {
readonly x: number;
readonly y: number;
@@ -318,6 +318,7 @@ export declare class DragRef {
withBoundaryElement(boundaryElement: ElementRef | HTMLElement | null): this;
withDirection(direction: Direction): this;
withHandles(handles: (HTMLElement | ElementRef)[]): this;
+ withParent(parent: DragRef | null): this;
withPlaceholderTemplate(template: DragHelperTemplate | null): this;
withPreviewTemplate(template: DragPreviewTemplate | null): this;
withRootElement(rootElement: ElementRef | HTMLElement): this;