Skip to content

Commit 6fe84f3

Browse files
crisbetovivian-hu-zz
authored andcommitted
fix(drag-drop): error if drag item is destroyed zone has stabilized (#13978)
We initialize the `rootElement` in an `NgZone.onStable` callback, however if the zone doesn't stabilize before the `cdkDrag` is destroyed, we'll throw an error because there is no `rootElement`. These changes add an extra check to ensure that we don't throw.
1 parent 6c0fd37 commit 6fe84f3

File tree

2 files changed

+36
-19
lines changed

2 files changed

+36
-19
lines changed

src/cdk/drag-drop/drag.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,14 @@ describe('CdkDrag', () => {
427427
expect(event.stopPropagation).toHaveBeenCalled();
428428
}));
429429

430+
it('should not throw if destroyed before the first change detection run', fakeAsync(() => {
431+
const fixture = createComponent(StandaloneDraggable);
432+
433+
expect(() => {
434+
fixture.destroy();
435+
}).not.toThrow();
436+
}));
437+
430438
});
431439

432440
describe('draggable with a handle', () => {

src/cdk/drag-drop/drag.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
175175
/** Subscription to the event that is dispatched when the user lifts their pointer. */
176176
private _pointerUpSubscription = Subscription.EMPTY;
177177

178+
/** Subscription to the stream that initializes the root element. */
179+
private _rootElementInitSubscription = Subscription.EMPTY;
180+
178181
/** Elements that can be used to drag the draggable item. */
179182
@ContentChildren(CdkDragHandle, {descendants: true}) _handles: QueryList<CdkDragHandle>;
180183

@@ -265,30 +268,36 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
265268
// element to be in the proper place in the DOM. This is mostly relevant
266269
// for draggable elements inside portals since they get stamped out in
267270
// their original DOM position and then they get transferred to the portal.
268-
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {
269-
const rootElement = this._rootElement = this._getRootElement();
270-
rootElement.addEventListener('mousedown', this._pointerDown, passiveEventListenerOptions);
271-
rootElement.addEventListener('touchstart', this._pointerDown, passiveEventListenerOptions);
272-
toggleNativeDragInteractions(rootElement , false);
273-
});
271+
this._rootElementInitSubscription = this._ngZone.onStable.asObservable()
272+
.pipe(take(1))
273+
.subscribe(() => {
274+
const rootElement = this._rootElement = this._getRootElement();
275+
rootElement.addEventListener('mousedown', this._pointerDown, passiveEventListenerOptions);
276+
rootElement.addEventListener('touchstart', this._pointerDown, passiveEventListenerOptions);
277+
toggleNativeDragInteractions(rootElement , false);
278+
});
274279
}
275280

276281
ngOnDestroy() {
277-
this._rootElement.removeEventListener('mousedown', this._pointerDown,
278-
passiveEventListenerOptions);
279-
this._rootElement.removeEventListener('touchstart', this._pointerDown,
280-
passiveEventListenerOptions);
281-
this._destroyPreview();
282-
this._destroyPlaceholder();
283-
284-
// Do this check before removing from the registry since it'll
285-
// stop being considered as dragged once it is removed.
286-
if (this._isDragging()) {
287-
// Since we move out the element to the end of the body while it's being
288-
// dragged, we have to make sure that it's removed if it gets destroyed.
289-
this._removeElement(this._rootElement);
282+
// The directive might have been destroyed before the root element is initialized.
283+
if (this._rootElement) {
284+
this._rootElement.removeEventListener('mousedown', this._pointerDown,
285+
passiveEventListenerOptions);
286+
this._rootElement.removeEventListener('touchstart', this._pointerDown,
287+
passiveEventListenerOptions);
288+
289+
// Do this check before removing from the registry since it'll
290+
// stop being considered as dragged once it is removed.
291+
if (this._isDragging()) {
292+
// Since we move out the element to the end of the body while it's being
293+
// dragged, we have to make sure that it's removed if it gets destroyed.
294+
this._removeElement(this._rootElement);
295+
}
290296
}
291297

298+
this._rootElementInitSubscription.unsubscribe();
299+
this._destroyPreview();
300+
this._destroyPlaceholder();
292301
this._nextSibling = null;
293302
this._dragDropRegistry.removeDragItem(this);
294303
this._removeSubscriptions();

0 commit comments

Comments
 (0)