Skip to content

Commit 6a4e57a

Browse files
committed
feat(drag-drop): add service for attaching drag&drop to arbitrary DOM nodes
* Adds the `DragDrop` service that simplifies the construction logic for `DragRef` and `DropListRef` and allows consumers to attach drag&drop functionality to arbitrary DOM nodes, rather than having to go through the directives. * Reworks `DragRef` and `DragDropRef` to make them easier to construct. * Normalizes some of the instances where some parameters only accept an `ElementRef`, whereas others accept `ElementRef | HTMLElement`.
1 parent bc563b7 commit 6a4e57a

File tree

9 files changed

+348
-88
lines changed

9 files changed

+348
-88
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
createTouchEvent,
2222
} from '@angular/cdk/testing';
2323
import {Directionality} from '@angular/cdk/bidi';
24+
import {of as observbleOf} from 'rxjs';
2425
import {CdkDrag, CDK_DRAG_CONFIG} from './drag';
2526
import {CdkDragDrop} from '../drag-events';
2627
import {moveItemInArray} from '../drag-utils';
@@ -972,7 +973,7 @@ describe('CdkDrag', () => {
972973
it('should dispatch the correct `dropped` event in RTL horizontal drop zone', fakeAsync(() => {
973974
const fixture = createComponent(DraggableInHorizontalDropZone, [{
974975
provide: Directionality,
975-
useValue: ({value: 'rtl'})
976+
useValue: ({value: 'rtl', change: observbleOf()})
976977
}]);
977978

978979
fixture.nativeElement.setAttribute('dir', 'rtl');
@@ -1128,7 +1129,7 @@ describe('CdkDrag', () => {
11281129
it('should pass the proper direction to the preview in rtl', fakeAsync(() => {
11291130
const fixture = createComponent(DraggableInDropZone, [{
11301131
provide: Directionality,
1131-
useValue: ({value: 'rtl'})
1132+
useValue: ({value: 'rtl', change: observbleOf()})
11321133
}]);
11331134

11341135
fixture.detectChanges();

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

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {CDK_DRAG_PARENT} from '../drag-parent';
4747
import {DragRef, DragRefConfig} from '../drag-ref';
4848
import {DropListRef} from '../drop-list-ref';
4949
import {CdkDropListInternal as CdkDropList} from './drop-list';
50+
import {DragDrop} from '../drag-drop';
5051

5152
/** Injection token that can be used to configure the behavior of `CdkDrag`. */
5253
export const CDK_DRAG_CONFIG = new InjectionToken<DragRefConfig>('CDK_DRAG_CONFIG', {
@@ -160,24 +161,41 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
160161
@Inject(DOCUMENT) private _document: any,
161162
private _ngZone: NgZone,
162163
private _viewContainerRef: ViewContainerRef,
163-
private _viewportRuler: ViewportRuler,
164-
private _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>,
165-
@Inject(CDK_DRAG_CONFIG) private _config: DragRefConfig,
166-
@Optional() private _dir: Directionality) {
167-
168-
const ref = this._dragRef = new DragRef(element, this._document, this._ngZone,
169-
this._viewContainerRef, this._viewportRuler, this._dragDropRegistry,
170-
this._config, this.dropContainer ? this.dropContainer._dropListRef : undefined,
171-
this._dir);
172-
ref.data = this;
173-
ref.beforeStarted.subscribe(() => {
164+
viewportRuler: ViewportRuler,
165+
dragDropRegistry: DragDropRegistry<DragRef, DropListRef>,
166+
@Inject(CDK_DRAG_CONFIG) config: DragRefConfig,
167+
@Optional() dir: Directionality,
168+
169+
/**
170+
* @deprecated `viewportRuler` and `dragDropRegistry` parameters
171+
* to be removed. Also `dragDrop` parameter to be made required.
172+
* @breaking-change 8.0.0.
173+
*/
174+
dragDrop?: DragDrop) {
175+
176+
// @breaking-change 8.0.0 Remove null check once the paramter is made required.
177+
if (dragDrop) {
178+
this._dragRef = dragDrop.createDrag(element, config);
179+
} else {
180+
this._dragRef = new DragRef(element, config, _document, _ngZone, viewportRuler,
181+
dragDropRegistry);
182+
}
183+
184+
this._dragRef.data = this;
185+
this._dragRef.beforeStarted.subscribe(() => {
186+
const ref = this._dragRef;
187+
174188
if (!ref.isDragging()) {
175189
ref.disabled = this.disabled;
176190
ref.lockAxis = this.lockAxis;
177191
ref.withBoundaryElement(this._getBoundaryElement());
192+
193+
if (dir) {
194+
ref.withDirection(dir.value);
195+
}
178196
}
179197
});
180-
this._proxyEvents(ref);
198+
this._proxyEvents(this._dragRef);
181199
}
182200

183201
/**
@@ -215,8 +233,16 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
215233

216234
this._dragRef
217235
.withRootElement(rootElement)
218-
.withPlaceholderTemplate(this._placeholderTemplate)
219-
.withPreviewTemplate(this._previewTemplate);
236+
.withPlaceholderTemplate(this._placeholderTemplate ? {
237+
templateRef: this._placeholderTemplate.templateRef,
238+
data: this._placeholderTemplate.data,
239+
viewContainer: this._viewContainerRef
240+
} : null)
241+
.withPreviewTemplate(this._previewTemplate ? {
242+
templateRef: this._previewTemplate.templateRef,
243+
data: this._previewTemplate.data,
244+
viewContainer: this._viewContainerRef
245+
} : null);
220246

221247
this._handles.changes
222248
.pipe(startWith(this._handles))

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

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
ChangeDetectorRef,
2222
SkipSelf,
2323
Inject,
24+
AfterContentInit,
2425
} from '@angular/core';
2526
import {DOCUMENT} from '@angular/common';
2627
import {Directionality} from '@angular/cdk/bidi';
@@ -31,6 +32,9 @@ import {CDK_DROP_LIST_CONTAINER, CdkDropListContainer} from '../drop-list-contai
3132
import {CdkDropListGroup} from './drop-list-group';
3233
import {DropListRef} from '../drop-list-ref';
3334
import {DragRef} from '../drag-ref';
35+
import {DragDrop} from '../drag-drop';
36+
import {Subject} from 'rxjs';
37+
import {startWith, takeUntil} from 'rxjs/operators';
3438

3539
/** Counter used to generate unique ids for drop zones. */
3640
let _uniqueIdCounter = 0;
@@ -61,15 +65,22 @@ export interface CdkDropListInternal extends CdkDropList {}
6165
'[class.cdk-drop-list-dragging]': '_dropListRef.isDragging()'
6266
}
6367
})
64-
export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
68+
export class CdkDropList<T = any> implements CdkDropListContainer, AfterContentInit, OnDestroy {
69+
/** Emits when the list has been destroyed. */
70+
private _destroyed = new Subject<void>();
71+
6572
/** Keeps track of the drop lists that are currently on the page. */
6673
private static _dropLists: CdkDropList[] = [];
6774

6875
/** Reference to the underlying drop list instance. */
6976
_dropListRef: DropListRef<CdkDropList<T>>;
7077

7178
/** Draggable items in the container. */
72-
@ContentChildren(forwardRef(() => CdkDrag)) _draggables: QueryList<CdkDrag>;
79+
@ContentChildren(forwardRef(() => CdkDrag), {
80+
// Explicitly set to false since some of the logic below makes assumptions about it.
81+
// The `.withItems` call below should be updated if we ever need to switch this to `true`.
82+
descendants: false
83+
}) _draggables: QueryList<CdkDrag>;
7384

7485
/**
7586
* Other draggable containers that this container is connected to and into which the
@@ -130,34 +141,51 @@ export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
130141
sorted: EventEmitter<CdkDragSortEvent<T>> = new EventEmitter<CdkDragSortEvent<T>>();
131142

132143
constructor(
144+
/** Element that the drop list is attached to. */
133145
public element: ElementRef<HTMLElement>,
134146
dragDropRegistry: DragDropRegistry<DragRef, DropListRef>,
135147
private _changeDetectorRef: ChangeDetectorRef,
136-
@Optional() dir?: Directionality,
148+
@Optional() private _dir?: Directionality,
137149
@Optional() @SkipSelf() private _group?: CdkDropListGroup<CdkDropList>,
138-
// @breaking-change 8.0.0 `_document` parameter to be made required.
139-
@Optional() @Inject(DOCUMENT) _document?: any) {
140-
150+
@Optional() @Inject(DOCUMENT) _document?: any,
151+
152+
/**
153+
* @deprecated `dragDropRegistry` and `_document` parameters to be removed.
154+
* Also `dragDrop` parameter to be made required.
155+
* @breaking-change 8.0.0.
156+
*/
157+
dragDrop?: DragDrop) {
158+
159+
// @breaking-change 8.0.0 Remove null check once `dragDrop` parameter is made required.
160+
if (dragDrop) {
161+
this._dropListRef = dragDrop.createDropList(element);
162+
} else {
163+
this._dropListRef = new DropListRef(element, dragDropRegistry, _document || document);
164+
}
141165

142-
// @breaking-change 8.0.0 Remove || once `_document` parameter is required.
143-
const ref = this._dropListRef = new DropListRef(element, dragDropRegistry,
144-
_document || document, dir);
145-
ref.data = this;
146-
ref.enterPredicate = (drag: DragRef<CdkDrag>, drop: DropListRef<CdkDropList>) => {
166+
this._dropListRef.data = this;
167+
this._dropListRef.enterPredicate = (drag: DragRef<CdkDrag>, drop: DropListRef<CdkDropList>) => {
147168
return this.enterPredicate(drag.data, drop.data);
148169
};
149-
this._syncInputs(ref);
150-
this._proxyEvents(ref);
170+
this._syncInputs(this._dropListRef);
171+
this._proxyEvents(this._dropListRef);
151172
CdkDropList._dropLists.push(this);
152173

153174
if (_group) {
154175
_group._items.add(this);
155176
}
156177
}
157178

179+
ngAfterContentInit() {
180+
this._draggables.changes
181+
.pipe(startWith(this._draggables), takeUntil(this._destroyed))
182+
.subscribe((items: QueryList<CdkDrag>) => {
183+
this._dropListRef.withItems(items.map(drag => drag._dragRef));
184+
});
185+
}
186+
158187
ngOnDestroy() {
159188
const index = CdkDropList._dropLists.indexOf(this);
160-
this._dropListRef.dispose();
161189

162190
if (index > -1) {
163191
CdkDropList._dropLists.splice(index, 1);
@@ -166,6 +194,10 @@ export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
166194
if (this._group) {
167195
this._group._items.delete(this);
168196
}
197+
198+
this._dropListRef.dispose();
199+
this._destroyed.next();
200+
this._destroyed.complete();
169201
}
170202

171203
/** Starts dragging an item. */
@@ -249,6 +281,12 @@ export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
249281

250282
/** Syncs the inputs of the CdkDropList with the options of the underlying DropListRef. */
251283
private _syncInputs(ref: DropListRef<CdkDropList>) {
284+
if (this._dir) {
285+
this._dir.change
286+
.pipe(startWith(this._dir.value), takeUntil(this._destroyed))
287+
.subscribe(value => ref.withDirection(value));
288+
}
289+
252290
ref.beforeStarted.subscribe(() => {
253291
const siblings = coerceArray(this.connectedTo).map(drop => {
254292
return typeof drop === 'string' ?
@@ -266,8 +304,7 @@ export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
266304
ref.lockAxis = this.lockAxis;
267305
ref
268306
.connectedTo(siblings.filter(drop => drop && drop !== this).map(list => list._dropListRef))
269-
.withOrientation(this.orientation)
270-
.withItems(this._draggables.map(drag => drag._dragRef));
307+
.withOrientation(this.orientation);
271308
});
272309
}
273310

src/cdk/drag-drop/drag-drop-module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {CdkDrag} from './directives/drag';
1313
import {CdkDragHandle} from './directives/drag-handle';
1414
import {CdkDragPreview} from './directives/drag-preview';
1515
import {CdkDragPlaceholder} from './directives/drag-placeholder';
16+
import {DragDrop} from './drag-drop';
1617

1718
@NgModule({
1819
declarations: [
@@ -31,5 +32,8 @@ import {CdkDragPlaceholder} from './directives/drag-placeholder';
3132
CdkDragPreview,
3233
CdkDragPlaceholder,
3334
],
35+
providers: [
36+
DragDrop,
37+
]
3438
})
3539
export class DragDropModule {}

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Injectable, Inject, NgZone, ElementRef} from '@angular/core';
10+
import {DOCUMENT} from '@angular/common';
11+
import {ViewportRuler} from '@angular/cdk/scrolling';
12+
import {DragRef, DragRefConfig} from './drag-ref';
13+
import {DropListRef} from './drop-list-ref';
14+
import {DragDropRegistry} from './drag-drop-registry';
15+
16+
/**
17+
* Service that allows for drag&drop functionality to be attached to DOM elements.
18+
*/
19+
@Injectable({providedIn: 'root'})
20+
export class DragDrop {
21+
constructor(
22+
@Inject(DOCUMENT) private _document: any,
23+
private _ngZone: NgZone,
24+
private _viewportRuler: ViewportRuler,
25+
private _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>) {}
26+
27+
/**
28+
* Turns an element into a draggable item.
29+
* @param element Element to which to attach the dragging functionality.
30+
* @param config Object used to configure the dragging behavior.
31+
*/
32+
createDrag<T = any>(element: ElementRef<HTMLElement> | HTMLElement,
33+
config: DragRefConfig = {
34+
dragStartThreshold: 5,
35+
pointerDirectionChangeThreshold: 5
36+
}): DragRef<T> {
37+
38+
return new DragRef<T>(element, config, this._document, this._ngZone, this._viewportRuler,
39+
this._dragDropRegistry);
40+
}
41+
42+
/**
43+
* Turns an element into a drop list.
44+
* @param element Element to which to attach the drop list functionality.
45+
*/
46+
createDropList<T = any>(element: ElementRef<HTMLElement> | HTMLElement): DropListRef<T> {
47+
return new DropListRef<T>(element, this._dragDropRegistry, this._document);
48+
}
49+
}

0 commit comments

Comments
 (0)