From be4d0d1f2aa55025c982b56d8a0552e4ccbe1865 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Fri, 31 Jan 2020 22:04:09 +0100 Subject: [PATCH] fix(drag-drop): error when dragging items inside transplanted views with OnPush change detection This is a bit of an edge case, but nevertheless it's something that can happen. The way the dragging sequence works is that the drop list keeps track of its items via `ContentChildren` and it registers itself with them once they're picked up by the `QueryList`. If an item doesn't have a container when the drag sequence is started, it is considered a "standalone" drag. This becomes a problem with transplanted views (e.g. ones created by `cdk-table`) that use OnPush where we could end up in a situation where the items aren't picked up during the first pass, but when the user starts dragging, we trigger change detection and the `QueryList` is updated, but at that point it's too late, because we've already started the dragging sequence with a different set of information. These changes work around the issue by assigning the container manually again through the drag directive. A couple of notes: * This is a second iteration of the fix. I went with this approach, because it doesn't require hacky timeouts and triggering change detection again. See the origin approach here: https://github.com/angular/components/commit/22afc3723258e6431b4603d96442daaffaa02f22 * It's difficult to capture this in a test, because we'd have to duplicate half of the logic from `CdkTable` and we might still not be able to get the same change detection timing due to `TestBed`. Fixes #18341. --- src/cdk/drag-drop/directives/drag.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/cdk/drag-drop/directives/drag.ts b/src/cdk/drag-drop/directives/drag.ts index e5dddd3331b1..8550a1519286 100644 --- a/src/cdk/drag-drop/directives/drag.ts +++ b/src/cdk/drag-drop/directives/drag.ts @@ -206,6 +206,17 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { this._assignDefaults(config); } + // Note that usually the container is assigned when the drop list is picks up the item, but in + // some cases (mainly transplanted views with OnPush, see #18341) we may end up in a situation + // where there are no items on the first change detection pass, but the items get picked up as + // soon as the user triggers another pass by dragging. This is a problem, because the item would + // have to switch from standalone mode to drag mode in the middle of the dragging sequence which + // is too late since the two modes save different kinds of information. We work around it by + // assigning the drop container both from here and the list. + if (dropContainer) { + this._dragRef._withDropContainer(dropContainer._dropListRef); + } + this._syncInputs(this._dragRef); this._handleEvents(this._dragRef); }