Skip to content

Commit

Permalink
fix(drag-drop): update root element if selector changes (#14426)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
crisbeto authored and jelbourn committed Dec 19, 2018
1 parent ecaec18 commit 14b90db
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 15 deletions.
24 changes: 23 additions & 1 deletion src/cdk/drag-drop/directives/drag.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -2999,7 +3020,7 @@ class ConnectedDropZonesViaGroupDirective extends ConnectedDropZones {
<div #dragRoot class="alternate-root" style="width: 200px; height: 200px; background: hotpink">
<div
cdkDrag
cdkDragRootElement=".alternate-root"
[cdkDragRootElement]="rootElementSelector"
#dragElement
style="width: 100px; height: 100px; background: red;"></div>
</div>
Expand All @@ -3009,6 +3030,7 @@ class DraggableWithAlternateRoot {
@ViewChild('dragElement') dragElement: ElementRef<HTMLElement>;
@ViewChild('dragRoot') dragRoot: ElementRef<HTMLElement>;
@ViewChild(CdkDrag) dragInstance: CdkDrag;
rootElementSelector: string;
}


Expand Down
36 changes: 23 additions & 13 deletions src/cdk/drag-drop/directives/drag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -70,7 +72,7 @@ export function CDK_DRAG_CONFIG_FACTORY(): DragRefConfig {
},
providers: [{provide: CDK_DRAG_PARENT, useExisting: CdkDrag}]
})
export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
/** Subscription to the stream that initializes the root element. */
private _rootElementInitSubscription = Subscription.EMPTY;

Expand Down Expand Up @@ -214,14 +216,7 @@ export class CdkDrag<T = any> 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<CdkDragHandle>) => {
Expand All @@ -230,18 +225,33 @@ export class CdkDrag<T = any> 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`. */
Expand Down
3 changes: 2 additions & 1 deletion tools/public_api_guard/cdk/drag-drop.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export declare const CDK_DROP_LIST: InjectionToken<CdkDropListContainer<any>>;

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

export declare class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
export declare class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
_dragRef: DragRef<CdkDrag<T>>;
_handles: QueryList<CdkDragHandle>;
_placeholderTemplate: CdkDragPlaceholder;
Expand All @@ -31,6 +31,7 @@ export declare class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
getPlaceholderElement(): HTMLElement;
getRootElement(): HTMLElement;
ngAfterViewInit(): void;
ngOnChanges(changes: SimpleChanges): void;
ngOnDestroy(): void;
reset(): void;
}
Expand Down

0 comments on commit 14b90db

Please sign in to comment.