Как я могу сделать MatDialog перетаскиваемый / угловой материал
Можно ли сделать диалог угловых материалов перетаскиваемым? Я установил angular2-draggable и, конечно, могу использовать функциональность на всех других элементах.
Но поскольку диалоги создаются динамически, я не могу использовать ngDraggable для специального элемента или использовать переменную шаблона.
2 ответа
Поскольку официального решения для этого не существует, я напишу пользовательскую директиву, которая будет применяться к заголовку диалога, и сделаю всю работу за нас:
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 {
Основная идея здесь заключается в использовании MatDialogRef.updatePosition
метод обновления позиции диалога. Под капотом этот метод изменяет значения margin-top|margin-left, и кто-то может утверждать, что это не лучший вариант, и было бы лучше, если бы мы использовали преобразование, но я просто хочу показать пример того, как мы можем сделать это без каких-либо трюки и с помощью встроенных сервисов.
Нам также нужно вставить MatDialogContainer в нашу директиву, чтобы мы могли получить начальную позицию контейнера диалога. Мы должны вычислить начальное смещение, потому что библиотека материалов Angular использует flex для центрирования диалога, и она не дает нам определенные верхние / левые значения.
Диалог-перетаскиваемости-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));
}
Как только вы можете закрыть диалоговое окно, я сохраняю последнее смещение.
Таким образом, диалог запоминает, где он был закрыт
В вашем модуле импортируйте перетаскивание cdk
import { DragDropModule } from '@angular/cdk/drag-drop';
а в html, где, например, есть диалог, просто добавьте к любому элементу html. Я добавил к первому элементу, а затем я могу перетащить диалог куда угодно.
<mat-dialog-content cdkDrag cdkDragRootElement=".cdk-overlay-pane" cdkDragHandle>
content...
</mat-dialog-content>
В случае, если кто-то еще столкнется с этим, на самом деле нет никакого смысла использовать cdkDrag и cdkDragHandle для того же элемента, что и в примерах здесь. Соответствующую проблему GH можно найти здесь:
В angular2-draggable
, ты используешь ngDraggable
чтобы сделать элемент перетаскиваемым. где ngDraggable
является директивой, и в вашей ситуации вы должны приложить это ngDraggable
Директива динамически с вашим диалогом, который создается динамически.
Хотя официально нет возможности динамически добавлять директиву, но в следующих вопросах обсуждались некоторые грязные приемы для динамического добавления директивы.