Skip to content

Commit 1156277

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 9914970 commit 1156277

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 observableOf} from 'rxjs';
2425
import {CdkDrag, CDK_DRAG_CONFIG} from './drag';
2526
import {CdkDragDrop} from '../drag-events';
2627
import {moveItemInArray} from '../drag-utils';
@@ -1082,7 +1083,7 @@ describe('CdkDrag', () => {
10821083
it('should dispatch the correct `dropped` event in RTL horizontal drop zone', fakeAsync(() => {
10831084
const fixture = createComponent(DraggableInHorizontalDropZone, [{
10841085
provide: Directionality,
1085-
useValue: ({value: 'rtl'})
1086+
useValue: ({value: 'rtl', change: observableOf()})
10861087
}]);
10871088

10881089
fixture.nativeElement.setAttribute('dir', 'rtl');
@@ -1238,7 +1239,7 @@ describe('CdkDrag', () => {
12381239
it('should pass the proper direction to the preview in rtl', fakeAsync(() => {
12391240
const fixture = createComponent(DraggableInDropZone, [{
12401241
provide: Directionality,
1241-
useValue: ({value: 'rtl'})
1242+
useValue: ({value: 'rtl', change: observableOf()})
12421243
}]);
12431244

12441245
fixture.detectChanges();

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

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

5455
/** Injection token that can be used to configure the behavior of `CdkDrag`. */
5556
export const CDK_DRAG_CONFIG = new InjectionToken<DragRefConfig>('CDK_DRAG_CONFIG', {
@@ -168,27 +169,52 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
168169
@Inject(DOCUMENT) private _document: any,
169170
private _ngZone: NgZone,
170171
private _viewContainerRef: ViewContainerRef,
171-
private _viewportRuler: ViewportRuler,
172-
private _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>,
173-
@Inject(CDK_DRAG_CONFIG) private _config: DragRefConfig,
174-
@Optional() private _dir: Directionality) {
175-
176-
const ref = this._dragRef = new DragRef(element, this._document, this._ngZone,
177-
this._viewContainerRef, this._viewportRuler, this._dragDropRegistry,
178-
this._config, this.dropContainer ? this.dropContainer._dropListRef : undefined,
179-
this._dir);
180-
ref.data = this;
181-
ref.beforeStarted.subscribe(() => {
172+
viewportRuler: ViewportRuler,
173+
dragDropRegistry: DragDropRegistry<DragRef, DropListRef>,
174+
@Inject(CDK_DRAG_CONFIG) config: DragRefConfig,
175+
@Optional() dir: Directionality,
176+
177+
/**
178+
* @deprecated `viewportRuler` and `dragDropRegistry` parameters
179+
* to be removed. Also `dragDrop` parameter to be made required.
180+
* @breaking-change 8.0.0.
181+
*/
182+
dragDrop?: DragDrop) {
183+
184+
// @breaking-change 8.0.0 Remove null check once the paramter is made required.
185+
if (dragDrop) {
186+
this._dragRef = dragDrop.createDrag(element, config);
187+
} else {
188+
this._dragRef = new DragRef(element, config, _document, _ngZone, viewportRuler,
189+
dragDropRegistry);
190+
}
191+
192+
this._dragRef.data = this;
193+
this._dragRef.beforeStarted.subscribe(() => {
194+
const ref = this._dragRef;
195+
182196
if (!ref.isDragging()) {
183197
ref.disabled = this.disabled;
184198
ref.lockAxis = this.lockAxis;
185199
ref
186200
.withBoundaryElement(this._getBoundaryElement())
187-
.withPlaceholderTemplate(this._placeholderTemplate)
188-
.withPreviewTemplate(this._previewTemplate);
201+
.withPlaceholderTemplate(this._placeholderTemplate ? {
202+
templateRef: this._placeholderTemplate.templateRef,
203+
data: this._placeholderTemplate.data,
204+
viewContainer: this._viewContainerRef
205+
} : null)
206+
.withPreviewTemplate(this._previewTemplate ? {
207+
templateRef: this._previewTemplate.templateRef,
208+
data: this._previewTemplate.data,
209+
viewContainer: this._viewContainerRef
210+
} : null);
211+
212+
if (dir) {
213+
ref.withDirection(dir.value);
214+
}
189215
}
190216
});
191-
this._proxyEvents(ref);
217+
this._proxyEvents(this._dragRef);
192218
}
193219

194220
/**

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;
@@ -62,15 +66,22 @@ export interface CdkDropListInternal extends CdkDropList {}
6266
'[class.cdk-drop-list-receiving]': '_dropListRef.isReceiving()',
6367
}
6468
})
65-
export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
69+
export class CdkDropList<T = any> implements CdkDropListContainer, AfterContentInit, OnDestroy {
70+
/** Emits when the list has been destroyed. */
71+
private _destroyed = new Subject<void>();
72+
6673
/** Keeps track of the drop lists that are currently on the page. */
6774
private static _dropLists: CdkDropList[] = [];
6875

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

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

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

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

146-
// @breaking-change 8.0.0 Remove || once `_document` parameter is required.
147-
const ref = this._dropListRef = new DropListRef(element, dragDropRegistry,
148-
_document || document, dir);
149-
ref.data = this;
150-
ref.enterPredicate = (drag: DragRef<CdkDrag>, drop: DropListRef<CdkDropList>) => {
170+
this._dropListRef.data = this;
171+
this._dropListRef.enterPredicate = (drag: DragRef<CdkDrag>, drop: DropListRef<CdkDropList>) => {
151172
return this.enterPredicate(drag.data, drop.data);
152173
};
153-
this._syncInputs(ref);
154-
this._proxyEvents(ref);
174+
this._syncInputs(this._dropListRef);
175+
this._proxyEvents(this._dropListRef);
155176
CdkDropList._dropLists.push(this);
156177

157178
if (_group) {
158179
_group._items.add(this);
159180
}
160181
}
161182

183+
ngAfterContentInit() {
184+
this._draggables.changes
185+
.pipe(startWith(this._draggables), takeUntil(this._destroyed))
186+
.subscribe((items: QueryList<CdkDrag>) => {
187+
this._dropListRef.withItems(items.map(drag => drag._dragRef));
188+
});
189+
}
190+
162191
ngOnDestroy() {
163192
const index = CdkDropList._dropLists.indexOf(this);
164-
this._dropListRef.dispose();
165193

166194
if (index > -1) {
167195
CdkDropList._dropLists.splice(index, 1);
@@ -170,6 +198,10 @@ export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
170198
if (this._group) {
171199
this._group._items.delete(this);
172200
}
201+
202+
this._dropListRef.dispose();
203+
this._destroyed.next();
204+
this._destroyed.complete();
173205
}
174206

175207
/** Starts dragging an item. */
@@ -253,6 +285,12 @@ export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
253285

254286
/** Syncs the inputs of the CdkDropList with the options of the underlying DropListRef. */
255287
private _syncInputs(ref: DropListRef<CdkDropList>) {
288+
if (this._dir) {
289+
this._dir.change
290+
.pipe(startWith(this._dir.value), takeUntil(this._destroyed))
291+
.subscribe(value => ref.withDirection(value));
292+
}
293+
256294
ref.beforeStarted.subscribe(() => {
257295
const siblings = coerceArray(this.connectedTo).map(drop => {
258296
return typeof drop === 'string' ?
@@ -270,8 +308,7 @@ export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {
270308
ref.lockAxis = this.lockAxis;
271309
ref
272310
.connectedTo(siblings.filter(drop => drop && drop !== this).map(list => list._dropListRef))
273-
.withOrientation(this.orientation)
274-
.withItems(this._draggables.map(drag => drag._dragRef));
311+
.withOrientation(this.orientation);
275312
});
276313
}
277314

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)