From f6c9ad9a07705d9c418fca9d539a81f565ade12b Mon Sep 17 00:00:00 2001 From: crisbeto Date: Wed, 25 Mar 2020 20:43:31 +0100 Subject: [PATCH] fix(drag-drop): defer resolving scrollable parents until first drag Currently we resolve a drop list's scrollable parents inside `ngAfterViewInit`, but this might be too early if the element is being projected. These changes move the logic to the first time the user starts dragging which should guarantee that the DOM structure has settled. Fixes #18737. --- src/cdk/drag-drop/directives/drop-list.ts | 30 ++++++++++++++--------- tools/public_api_guard/cdk/drag-drop.d.ts | 3 +-- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/cdk/drag-drop/directives/drop-list.ts b/src/cdk/drag-drop/directives/drop-list.ts index 46697a432f88..33fb95164128 100644 --- a/src/cdk/drag-drop/directives/drop-list.ts +++ b/src/cdk/drag-drop/directives/drop-list.ts @@ -17,7 +17,6 @@ import { Directive, ChangeDetectorRef, SkipSelf, - AfterContentInit, Inject, } from '@angular/core'; import {Directionality} from '@angular/cdk/bidi'; @@ -59,10 +58,13 @@ export interface CdkDropListInternal extends CdkDropList {} '[class.cdk-drop-list-receiving]': '_dropListRef.isReceiving()', } }) -export class CdkDropList implements AfterContentInit, OnDestroy { +export class CdkDropList implements OnDestroy { /** Emits when the list has been destroyed. */ private _destroyed = new Subject(); + /** Whether the element's scrollable parents have been resolved. */ + private _scrollableParentsResolved: boolean; + /** Keeps track of the drop lists that are currently on the page. */ private static _dropLists: CdkDropList[] = []; @@ -183,16 +185,6 @@ export class CdkDropList implements AfterContentInit, OnDestroy { } } - ngAfterContentInit() { - // @breaking-change 11.0.0 Remove null check for _scrollDispatcher once it's required. - if (this._scrollDispatcher) { - const scrollableParents = this._scrollDispatcher - .getAncestorScrollContainers(this.element) - .map(scrollable => scrollable.getElementRef().nativeElement); - this._dropListRef.withScrollableParents(scrollableParents); - } - } - /** Registers an items with the drop list. */ addItem(item: CdkDrag): void { this._unsortedItems.add(item); @@ -321,6 +313,20 @@ export class CdkDropList implements AfterContentInit, OnDestroy { }); } + // Note that we resolve the scrollable parents here so that we delay the resolution + // as long as possible, ensuring that the element is in its final place in the DOM. + // @breaking-change 11.0.0 Remove null check for _scrollDispatcher once it's required. + if (!this._scrollableParentsResolved && this._scrollDispatcher) { + const scrollableParents = this._scrollDispatcher + .getAncestorScrollContainers(this.element) + .map(scrollable => scrollable.getElementRef().nativeElement); + this._dropListRef.withScrollableParents(scrollableParents); + + // Only do this once since it involves traversing the DOM and the parents + // shouldn't be able to change without the drop list being destroyed. + this._scrollableParentsResolved = true; + } + ref.disabled = this.disabled; ref.lockAxis = this.lockAxis; ref.sortingDisabled = coerceBooleanProperty(this.sortingDisabled); diff --git a/tools/public_api_guard/cdk/drag-drop.d.ts b/tools/public_api_guard/cdk/drag-drop.d.ts index 2d7e06cd44d2..5c510167dfce 100644 --- a/tools/public_api_guard/cdk/drag-drop.d.ts +++ b/tools/public_api_guard/cdk/drag-drop.d.ts @@ -145,7 +145,7 @@ export interface CdkDragStart { source: CdkDrag; } -export declare class CdkDropList implements AfterContentInit, OnDestroy { +export declare class CdkDropList implements OnDestroy { _dropListRef: DropListRef>; autoScrollDisabled: boolean; connectedTo: (CdkDropList | string)[] | CdkDropList | string; @@ -171,7 +171,6 @@ export declare class CdkDropList implements AfterContentInit, OnDestroy exit(item: CdkDrag): void; getItemIndex(item: CdkDrag): number; getSortedItems(): CdkDrag[]; - ngAfterContentInit(): void; ngOnDestroy(): void; removeItem(item: CdkDrag): void; start(): void;