Skip to content

Commit 14b90db

Browse files
crisbetojelbourn
authored andcommittedDec 19, 2018
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.
1 parent ecaec18 commit 14b90db

File tree

3 files changed

+48
-15
lines changed

3 files changed

+48
-15
lines changed
 

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

+23-1
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,27 @@ describe('CdkDrag', () => {
475475

476476
it('should be able to set an alternate drag root element', fakeAsync(() => {
477477
const fixture = createComponent(DraggableWithAlternateRoot);
478+
fixture.componentInstance.rootElementSelector = '.alternate-root';
479+
fixture.detectChanges();
480+
481+
const dragRoot = fixture.componentInstance.dragRoot.nativeElement;
482+
const dragElement = fixture.componentInstance.dragElement.nativeElement;
483+
484+
expect(dragRoot.style.transform).toBeFalsy();
485+
expect(dragElement.style.transform).toBeFalsy();
486+
487+
dragElementViaMouse(fixture, dragRoot, 50, 100);
488+
489+
expect(dragRoot.style.transform).toBe('translate3d(50px, 100px, 0px)');
490+
expect(dragElement.style.transform).toBeFalsy();
491+
}));
492+
493+
it('should handle the root element selector changing after init', fakeAsync(() => {
494+
const fixture = createComponent(DraggableWithAlternateRoot);
495+
fixture.detectChanges();
496+
tick();
497+
498+
fixture.componentInstance.rootElementSelector = '.alternate-root';
478499
fixture.detectChanges();
479500

480501
const dragRoot = fixture.componentInstance.dragRoot.nativeElement;
@@ -2999,7 +3020,7 @@ class ConnectedDropZonesViaGroupDirective extends ConnectedDropZones {
29993020
<div #dragRoot class="alternate-root" style="width: 200px; height: 200px; background: hotpink">
30003021
<div
30013022
cdkDrag
3002-
cdkDragRootElement=".alternate-root"
3023+
[cdkDragRootElement]="rootElementSelector"
30033024
#dragElement
30043025
style="width: 100px; height: 100px; background: red;"></div>
30053026
</div>
@@ -3009,6 +3030,7 @@ class DraggableWithAlternateRoot {
30093030
@ViewChild('dragElement') dragElement: ElementRef<HTMLElement>;
30103031
@ViewChild('dragRoot') dragRoot: ElementRef<HTMLElement>;
30113032
@ViewChild(CdkDrag) dragInstance: CdkDrag;
3033+
rootElementSelector: string;
30123034
}
30133035

30143036

‎src/cdk/drag-drop/directives/drag.ts

+23-13
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
QueryList,
2727
SkipSelf,
2828
ViewContainerRef,
29+
OnChanges,
30+
SimpleChanges,
2931
} from '@angular/core';
3032
import {coerceBooleanProperty} from '@angular/cdk/coercion';
3133
import {Observable, Subscription, Observer} from 'rxjs';
@@ -70,7 +72,7 @@ export function CDK_DRAG_CONFIG_FACTORY(): DragRefConfig {
7072
},
7173
providers: [{provide: CDK_DRAG_PARENT, useExisting: CdkDrag}]
7274
})
73-
export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
75+
export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
7476
/** Subscription to the stream that initializes the root element. */
7577
private _rootElementInitSubscription = Subscription.EMPTY;
7678

@@ -214,14 +216,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
214216
this._rootElementInitSubscription = this._ngZone.onStable.asObservable()
215217
.pipe(take(1))
216218
.subscribe(() => {
217-
const rootElement = this._getRootElement();
218-
219-
if (rootElement.nodeType !== this._document.ELEMENT_NODE) {
220-
throw Error(`cdkDrag must be attached to an element node. ` +
221-
`Currently attached to "${rootElement.nodeName}".`);
222-
}
223-
224-
this._dragRef.withRootElement(rootElement);
219+
this._updateRootElement();
225220
this._handles.changes
226221
.pipe(startWith(this._handles))
227222
.subscribe((handleList: QueryList<CdkDragHandle>) => {
@@ -230,18 +225,33 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
230225
});
231226
}
232227

228+
ngOnChanges(changes: SimpleChanges) {
229+
const rootSelectorChange = changes.rootElementSelector;
230+
231+
// We don't have to react to the first change since it's being
232+
// handled in `ngAfterViewInit` where it needs to be deferred.
233+
if (rootSelectorChange && !rootSelectorChange.firstChange) {
234+
this._updateRootElement();
235+
}
236+
}
237+
233238
ngOnDestroy() {
234239
this._rootElementInitSubscription.unsubscribe();
235240
this._dragRef.dispose();
236241
}
237242

238-
/** Gets the root draggable element, based on the `rootElementSelector`. */
239-
private _getRootElement(): HTMLElement {
243+
/** Syncs the root element with the `DragRef`. */
244+
private _updateRootElement() {
240245
const element = this.element.nativeElement;
241246
const rootElement = this.rootElementSelector ?
242-
getClosestMatchingAncestor(element, this.rootElementSelector) : null;
247+
getClosestMatchingAncestor(element, this.rootElementSelector) : element;
248+
249+
if (rootElement && rootElement.nodeType !== this._document.ELEMENT_NODE) {
250+
throw Error(`cdkDrag must be attached to an element node. ` +
251+
`Currently attached to "${rootElement.nodeName}".`);
252+
}
243253

244-
return rootElement || element;
254+
this._dragRef.withRootElement(rootElement || element);
245255
}
246256

247257
/** Gets the boundary element, based on the `boundaryElementSelector`. */

‎tools/public_api_guard/cdk/drag-drop.d.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export declare const CDK_DROP_LIST: InjectionToken<CdkDropListContainer<any>>;
66

77
export declare const CDK_DROP_LIST_CONTAINER: InjectionToken<CdkDropListContainer<any>>;
88

9-
export declare class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
9+
export declare class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
1010
_dragRef: DragRef<CdkDrag<T>>;
1111
_handles: QueryList<CdkDragHandle>;
1212
_placeholderTemplate: CdkDragPlaceholder;
@@ -31,6 +31,7 @@ export declare class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
3131
getPlaceholderElement(): HTMLElement;
3232
getRootElement(): HTMLElement;
3333
ngAfterViewInit(): void;
34+
ngOnChanges(changes: SimpleChanges): void;
3435
ngOnDestroy(): void;
3536
reset(): void;
3637
}

0 commit comments

Comments
 (0)
Please sign in to comment.