Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can i make a MatDialog draggable / Angular Material

Is it possible to make a Angular Material Dialog draggable? I installed angular2-draggable and can of course use the functionality on all other elements.

But because the dialogs are dynamically created i can not use ngDraggable on a special element or can use a template variable.

like image 512
HansDampfHH Avatar asked Nov 27 '17 12:11

HansDampfHH


People also ask

Does angular support drag-and-drop?

The @angular/cdk/drag-drop module provides you with a way to easily and declaratively create drag-and-drop interfaces, with support for free dragging, sorting within a list, transferring items between lists, animations, touch devices, custom drag handles, previews, and placeholders, in addition to horizontal lists and ...

How do I turn off drag-and-drop on CDK?

Disable dragging If you want to disable dragging for a particular drag item, you can do so by setting the cdkDragDisabled input on a cdkDrag item. Furthermore, you can disable an entire list using the cdkDropListDisabled input on a cdkDropList or a particular handle via cdkDragHandleDisabled on cdkDragHandle.

How do you drag-and-drop CDK?

When you check in the browser, it allows you to drag the item. It will not drop it in the list and will remain as it is when you leave the mouse pointer. The function onDrop takes care of dropping the item dragged in the position required.


2 Answers

Update since Angular Material 7

You can simply use cdkDrag directive from @angular/cdk/drag-drop

dialog.html

<h1 mat-dialog-title     cdkDrag    cdkDragRootElement=".cdk-overlay-pane"     cdkDragHandle>      Hi {{data.name}} </h1> 

Stackblitz Example

Previous answer:

Since there is no official solution for that, I'm going to write custom directive that will be applied on a dialog title and do all job for us:

dialog.html

@Component({   selector: 'app-simple-dialog',   template: `     <h1 mat-dialog-title mat-dialog-draggable-title>Hi {{data.name}}</h1>                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^     <div mat-dialog-content>       ...     </div>     <div mat-dialog-actions>       ...     </div>   ` }) export class SimpleDialogComponent { 

Ng-run Example

enter image description here

The basic idea here is to use MatDialogRef.updatePosition method for updating dialog position. Under the hood this method changes margin-top|margin-left values and someone can argue that it's not the best option here and it would be better if we used transform but I simply want to show an example of how we can do it without some tricks and with the help of the built-in services.

We also need to inject MatDialogContainer in our directive so that we can get initial position of dialog container. We have to calculate initial offset because Angular material library uses flex to center dialog and it doesn't get us specific top/left values.

dialog-draggable-title.directive.ts

import { Directive, HostListener, OnInit } from '@angular/core'; import { MatDialogContainer, MatDialogRef } from '@angular/material'; import { Subscription } from 'rxjs/Subscription'; import { Observable } from 'rxjs/Observable'; import { takeUntil } from 'rxjs/operators/takeUntil'; import 'rxjs/add/observable/fromEvent'; import { take } from 'rxjs/operators/take';  @Directive({   selector: '[mat-dialog-draggable-title]' }) export class DialogDraggableTitleDirective implements OnInit {    private _subscription: Subscription;    mouseStart: Position;    mouseDelta: Position;    offset: Position;    constructor(     private matDialogRef: MatDialogRef<any>,     private container: MatDialogContainer) {}    ngOnInit() {     this.offset = this._getOffset();   }    @HostListener('mousedown', ['$event'])   onMouseDown(event: MouseEvent) {     this.mouseStart = {x: event.pageX, y: event.pageY};      const mouseup$ = Observable.fromEvent(document, 'mouseup');     this._subscription = mouseup$.subscribe(() => this.onMouseup());      const mousemove$ = Observable.fromEvent(document, 'mousemove')       .pipe(takeUntil(mouseup$))       .subscribe((e: MouseEvent) => this.onMouseMove(e));      this._subscription.add(mousemove$);   }    onMouseMove(event: MouseEvent) {       this.mouseDelta = {x: (event.pageX - this.mouseStart.x), y: (event.pageY - this.mouseStart.y)};        this._updatePosition(this.offset.y + this.mouseDelta.y, this.offset.x + this.mouseDelta.x);   }    onMouseup() {     if (this._subscription) {       this._subscription.unsubscribe();       this._subscription = undefined;     }      if (this.mouseDelta) {       this.offset.x += this.mouseDelta.x;       this.offset.y += this.mouseDelta.y;     }   }    private _updatePosition(top: number, left: number) {     this.matDialogRef.updatePosition({       top: top + 'px',       left: left + 'px'     });   }    private _getOffset(): Position {     const box = this.container['_elementRef'].nativeElement.getBoundingClientRect();     return {       x: box.left + pageXOffset,       y: box.top + pageYOffset     };   } }   export interface Position {   x: number;   y: number; } 

Remember location

Since @Rolando asked:

I want to 'remember' where the modal was positioned so that when the button to open the modal is hit, the modal opens up where 'it was last located'.

let's try to support it.

In order to do that you can create some service where you will store dialog positions:

modal-position.cache.ts

@Injectable() export class ModalPositionCache {   private _cache = new Map<Type<any>, Position>();    set(dialog: Type<any>, position: Position) {     this._cache.set(dialog, position);   }    get(dialog: Type<any>): Position|null {     return this._cache.get(dialog);   } } 

now you need to inject this service in our directive:

dialog-draggable-title.directive.ts

export class DialogDraggableTitleDirective implements OnInit {   ...    constructor(     private matDialogRef: MatDialogRef<any>,     private container: MatDialogContainer,     private positionCache: ModalPositionCache   ) {}    ngOnInit() {     const dialogType = this.matDialogRef.componentInstance.constructor;     const cachedValue = this.positionCache.get(dialogType);     this.offset = cachedValue || this._getOffset();     this._updatePosition(this.offset.y, this.offset.x);      this.matDialogRef.beforeClose().pipe(take(1))       .subscribe(() => this.positionCache.set(dialogType, this.offset));   } 

As you can as soon as dialog is going to be closed i save last offset.

Ng-run Example

This way dialog remembers where it was closed

enter image description here

like image 189
yurzui Avatar answered Sep 20 '22 13:09

yurzui


in your module, import the cdk drag

import { DragDropModule } from '@angular/cdk/drag-drop'; 

and in html where dialog is for example, just add to any html element. i have added to first element and then i can drag the dialog anywhere i pick.

<mat-dialog-content cdkDrag cdkDragRootElement=".cdk-overlay-pane" cdkDragHandle>   content... </mat-dialog-content> 
like image 31
pinarella Avatar answered Sep 17 '22 13:09

pinarella