Как я могу сделать MatDialog перетаскиваемый/угловой материал

Можно ли перетаскивать диалоговое окно с угловым материалом? Я установил angular2-draggable и, конечно, может использовать функциональность для всех остальных элементов.

Но поскольку диалоги динамически создаются, я не могу использовать ngDraggable для специального элемента или использовать переменную шаблона.

Ответ 1

Поскольку для этого нет официального решения, я собираюсь написать настраиваемую директиву, которая будет применяться к заголовку диалога и выполнить всю работу для нас:

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

enter image description here

Основная идея здесь - использовать метод MatDialogRef.updatePosition для обновления позиции диалога. Под капотом этот метод изменяет значения margin-top | margin-left, и кто-то может утверждать, что это не лучший вариант здесь, и было бы лучше, если бы мы использовали преобразование, но я просто хочу показать пример того, как мы можем это сделать без каких-либо трюки и с помощью встроенных сервисов.

Нам также необходимо ввести MatDialogContainer в нашу директиву, чтобы мы могли получить начальную позицию диалогового контейнера. Мы должны рассчитать начальное смещение, потому что библиотека Углового материала использует диалог flex to center и не дает нам конкретных значений верхнего/левого значения.

Диалог-перетаскиваемости-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;
}

Помните местоположение

Поскольку @Rolando спросил:

Я хочу "вспомнить", где модаль был позиционирован так, что когда кнопка для открытия модала попадает, модаль открывается, где "он был последним".

попробуйте его поддержать.

Для этого вы можете создать некоторую услугу, в которой будут храниться диалоговые позиции:

модальный-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);
  }
}

теперь вам нужно ввести эту услугу в нашу директиву:

Диалог-перетаскиваемости-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));
  }

Как вы можете, как только диалог будет закрыт, я сохраняю последнее смещение.

Пример Ng-run

В этом случае диалог запоминает, где он был закрыт

enter image description here

Ответ 2

в angular2-draggable вы используете ngDraggable чтобы сделать элемент перетаскиваемым. где ngDraggable является директивой, и в вашей ситуации вы должны динамически ngDraggable эту директиву ngDraggable с динамическим диалогом.

Хотя официально, нет никакого способа добавить директиву динамически, но некоторые грязные трюки обсуждались в следующих вопросах, чтобы динамически добавлять директиву.

Как динамически добавить директиву?

Используйте директиву Angular2 в другой директиве