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.
This commit is contained in:
Kristiyan Kostadinov
2018-12-19 16:05:53 +01:00
committed by Jeremy Elbourn
parent ecaec183de
commit 14b90db522
3 changed files with 48 additions and 15 deletions
+23 -1
View File
@@ -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 {
<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>
@@ -3009,6 +3030,7 @@ class DraggableWithAlternateRoot {
@ViewChild('dragElement') dragElement: ElementRef<HTMLElement>;
@ViewChild('dragRoot') dragRoot: ElementRef<HTMLElement>;
@ViewChild(CdkDrag) dragInstance: CdkDrag;
rootElementSelector: string;
}
+23 -13
View File
@@ -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<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;
@@ -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>) => {
@@ -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;
return rootElement || element;
if (rootElement && 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 || element);
}
/** Gets the boundary element, based on the `boundaryElementSelector`. */
+2 -1
View File
@@ -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;
@@ -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;
}