From 14b90db5224958e7dbff361465e28dad37089bfc Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 19 Dec 2018 16:05:53 +0100 Subject: [PATCH] fix(drag-drop): update root element if selector changes (#14426) Currently the root element is only resolved once on init, however since it's an input, it could change. These changes add some logic to keep the element up to date. --- src/cdk/drag-drop/directives/drag.spec.ts | 24 ++++++++++++++- src/cdk/drag-drop/directives/drag.ts | 36 +++++++++++++++-------- tools/public_api_guard/cdk/drag-drop.d.ts | 3 +- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/cdk/drag-drop/directives/drag.spec.ts b/src/cdk/drag-drop/directives/drag.spec.ts index b3248a464f06..9a085349d50f 100644 --- a/src/cdk/drag-drop/directives/drag.spec.ts +++ b/src/cdk/drag-drop/directives/drag.spec.ts @@ -475,6 +475,27 @@ describe('CdkDrag', () => { it('should be able to set an alternate drag root element', fakeAsync(() => { const fixture = createComponent(DraggableWithAlternateRoot); + fixture.componentInstance.rootElementSelector = '.alternate-root'; + fixture.detectChanges(); + + const dragRoot = fixture.componentInstance.dragRoot.nativeElement; + const dragElement = fixture.componentInstance.dragElement.nativeElement; + + expect(dragRoot.style.transform).toBeFalsy(); + expect(dragElement.style.transform).toBeFalsy(); + + dragElementViaMouse(fixture, dragRoot, 50, 100); + + expect(dragRoot.style.transform).toBe('translate3d(50px, 100px, 0px)'); + expect(dragElement.style.transform).toBeFalsy(); + })); + + it('should handle the root element selector changing after init', fakeAsync(() => { + const fixture = createComponent(DraggableWithAlternateRoot); + fixture.detectChanges(); + tick(); + + fixture.componentInstance.rootElementSelector = '.alternate-root'; fixture.detectChanges(); const dragRoot = fixture.componentInstance.dragRoot.nativeElement; @@ -2999,7 +3020,7 @@ class ConnectedDropZonesViaGroupDirective extends ConnectedDropZones {
@@ -3009,6 +3030,7 @@ class DraggableWithAlternateRoot { @ViewChild('dragElement') dragElement: ElementRef; @ViewChild('dragRoot') dragRoot: ElementRef; @ViewChild(CdkDrag) dragInstance: CdkDrag; + rootElementSelector: string; } diff --git a/src/cdk/drag-drop/directives/drag.ts b/src/cdk/drag-drop/directives/drag.ts index b857318c78aa..bfbaf292e065 100644 --- a/src/cdk/drag-drop/directives/drag.ts +++ b/src/cdk/drag-drop/directives/drag.ts @@ -26,6 +26,8 @@ import { QueryList, SkipSelf, ViewContainerRef, + OnChanges, + SimpleChanges, } from '@angular/core'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; import {Observable, Subscription, Observer} from 'rxjs'; @@ -70,7 +72,7 @@ export function CDK_DRAG_CONFIG_FACTORY(): DragRefConfig { }, providers: [{provide: CDK_DRAG_PARENT, useExisting: CdkDrag}] }) -export class CdkDrag implements AfterViewInit, OnDestroy { +export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { /** Subscription to the stream that initializes the root element. */ private _rootElementInitSubscription = Subscription.EMPTY; @@ -214,14 +216,7 @@ export class CdkDrag implements AfterViewInit, OnDestroy { this._rootElementInitSubscription = this._ngZone.onStable.asObservable() .pipe(take(1)) .subscribe(() => { - const rootElement = this._getRootElement(); - - if (rootElement.nodeType !== this._document.ELEMENT_NODE) { - throw Error(`cdkDrag must be attached to an element node. ` + - `Currently attached to "${rootElement.nodeName}".`); - } - - this._dragRef.withRootElement(rootElement); + this._updateRootElement(); this._handles.changes .pipe(startWith(this._handles)) .subscribe((handleList: QueryList) => { @@ -230,18 +225,33 @@ export class CdkDrag implements AfterViewInit, OnDestroy { }); } + ngOnChanges(changes: SimpleChanges) { + const rootSelectorChange = changes.rootElementSelector; + + // We don't have to react to the first change since it's being + // handled in `ngAfterViewInit` where it needs to be deferred. + if (rootSelectorChange && !rootSelectorChange.firstChange) { + this._updateRootElement(); + } + } + ngOnDestroy() { this._rootElementInitSubscription.unsubscribe(); this._dragRef.dispose(); } - /** Gets the root draggable element, based on the `rootElementSelector`. */ - private _getRootElement(): HTMLElement { + /** Syncs the root element with the `DragRef`. */ + private _updateRootElement() { const element = this.element.nativeElement; const rootElement = this.rootElementSelector ? - getClosestMatchingAncestor(element, this.rootElementSelector) : null; + getClosestMatchingAncestor(element, this.rootElementSelector) : element; + + if (rootElement && rootElement.nodeType !== this._document.ELEMENT_NODE) { + throw Error(`cdkDrag must be attached to an element node. ` + + `Currently attached to "${rootElement.nodeName}".`); + } - return rootElement || element; + this._dragRef.withRootElement(rootElement || element); } /** Gets the boundary element, based on the `boundaryElementSelector`. */ diff --git a/tools/public_api_guard/cdk/drag-drop.d.ts b/tools/public_api_guard/cdk/drag-drop.d.ts index 6dc3498ec12f..53ab39df9819 100644 --- a/tools/public_api_guard/cdk/drag-drop.d.ts +++ b/tools/public_api_guard/cdk/drag-drop.d.ts @@ -6,7 +6,7 @@ export declare const CDK_DROP_LIST: InjectionToken>; export declare const CDK_DROP_LIST_CONTAINER: InjectionToken>; -export declare class CdkDrag implements AfterViewInit, OnDestroy { +export declare class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { _dragRef: DragRef>; _handles: QueryList; _placeholderTemplate: CdkDragPlaceholder; @@ -31,6 +31,7 @@ export declare class CdkDrag implements AfterViewInit, OnDestroy { getPlaceholderElement(): HTMLElement; getRootElement(): HTMLElement; ngAfterViewInit(): void; + ngOnChanges(changes: SimpleChanges): void; ngOnDestroy(): void; reset(): void; }