Est-il possible de rendre un dialogue de matériau angulaire déplaçable? J'ai installé angular2-draggable et peux bien sûr utiliser la fonctionnalité sur tous les autres éléments.
Mais comme les dialogues sont créés dynamiquement, je ne peux pas utiliser ngDraggable sur un élément spécial ni utiliser une variable de modèle.
Vous pouvez simplement utiliser cdkDrag directive from @angular/cdk/drag-drop
dialog.html
<h1 mat-dialog-title
cdkDrag
cdkDragRootElement=".cdk-overlay-pane"
cdkDragHandle>
Hi {{data.name}}
</h1>
Puisqu'il n'y a pas de solution officielle pour cela, je vais écrire une directive personnalisée qui sera appliquée à un titre de dialogue et fera tout le travail à notre place:
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 {
L'idée de base est d'utiliser la méthode MatDialogRef.updatePosition
pour mettre à jour la position du dialogue. Sous le capot, cette méthode modifie les valeurs margin-top | margin-left et quelqu'un peut dire que ce n'est pas la meilleure option et qu'il serait préférable d'utiliser la transformation, mais je veux simplement montrer un exemple de la façon dont nous pouvons le faire sans astuces et avec l'aide des services intégrés.
Nous devons également injecter MatDialogContainer dans notre directive afin d’obtenir la position initiale du conteneur de dialogue. Nous devons calculer le décalage initial, car la bibliothèque de matériaux angulaires utilise le dialogue flex au centre et ne nous donne pas de valeurs haut/gauche spécifiques.
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;
}
Depuis que @Rolando a demandé:
Je veux «me rappeler» où le modal était positionné de telle sorte que lorsque le bouton pour ouvrir le modal est frappé, le modal s'ouvre là où il était "dernier situé'.
essayons de le supporter.
Pour ce faire, vous pouvez créer un service dans lequel vous enregistrerez des positions de dialogue:
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);
}
}
maintenant vous devez injecter ce service dans notre 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));
}
Comme vous pouvez dès que le dialogue va être fermé, je sauvegarde le dernier décalage.
Le dialogue de cette façon se souvient où il a été fermé
dans angular2-draggable
, vous utilisez ngDraggable
pour rendre l'élément glissable . où ngDraggable
est une directive et, dans votre cas, vous devez attacher cette directive ngDraggable
de manière dynamique à votre boîte de dialogue créée de manière dynamique.
Bien qu’officiellement, il n’existe aucun moyen d’ajouter une directive de manière dynamique, mais certaines astuces ont été abordées dans les questions suivantes pour l’ajouter de manière dynamique.
Comment ajouter de manière dynamique une directive?
Utiliser la directive Angular2 dans l'hôte d'une autre directive