Reputation: 9487
I'm trying to create movable windows in Angular 7. To do this, I have an absolutely positioned div that windows will go inside of. I have an app-window-toolbar component and an app-window-container component. When the toolbar is dragged, I want to change the top and left CSS attributes of the app-window-container component. The app-window-toolbar component is a child of the app-window-container component.
app.component.html
<app-window-container *ngFor="let window of windows">
<app-window-toolbar>
</app-window-toolbar>
<div>Window content here</div>
</app-window-container>
window-toolbar.component.ts
@Component({
selector: "app-window-toolbar",
templateUrl: "./window-toolbar.component.html",
styleUrls: ["./window-toolbar.component.scss"]
})
export class WindowToolbarComponent implements OnInit {
moving = false
constructor(private elRef: ElementRef) { }
ngOnInit() {
}
startMove() {
this.moving = true
}
@HostListener("document:mousemove", ["$event"])
move(event: MouseEvent) {
if (this.moving) {
// Here I'm trying to access the parent element to set it's
// CSS properties. The mouse y position is being logged
// successfully, but the parent elements style is not being set.
console.log("mouse y: " + event.clientY)
this.elRef.nativeElement.parentElement.style.top = event.clientY
this.elRef.nativeElement.parentElement.style.left = event.clientX
}
}
@HostListener("document:mouseup")
stopMove() {
this.moving = false
}
}
window-toolbar.component.html
<div style="height: 100%; display: flex" (mousedown)="startMove()">
<div style="flex-grow: 1">
<ng-content></ng-content>
</div>
<div style="height: 100%">
<i class="material-icons md-light close-window-icon">close</i>
</div>
</div>
I'm not sure where I'm going wrong here. I'm also looking for feedback on whether this is the best way of doing this? From what I've read this style of DOM manipulation seems to be frowned upon, but I'm not sure I have much of choice to implement this feature. The only other thing I can think of doing is emitting events when the toolbar is dragged, and have the container listen to those events. Not sure how great the performance would be though.
Upvotes: 1
Views: 4050
Reputation: 9487
As was pointed out in the comments, there is a module in Angular Material that already solves this. I decided to go with this solution as once you start thinking about the edge cases, it doesn't make sense to roll your own solution.
It was easy to create a draggable window by doing the following:
Register the DragDropModule:
import {DragDropModule} from '@angular/cdk/drag-drop';
@NgModule({
declarations: [],
imports: [
DragDropModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Use the directives in your layout:
<div class="app-layout mat-typography">
<div class="app-menu"></div>
<div class="app-content">
<div *ngFor="let window of windows" class="window-container" cdkDrag cdkDragBoundary=".app-content">
<div class="window-toolbar" cdkDragHandle></div>
<div class="window-content"></div>
</div>
</div>
</div>
Upvotes: 2
Reputation: 214175
I guess it doesn't work since style.top|left
properties should take px
values but you're setting numbers.
this.elRef.nativeElement.parentElement.style.top = event.clientY + 'px'
/\
don't forget it
Also, if you don't want your toolbar jumps when starting dragging then you should take into account the shift between your cursor and top left corner of that toolbar.
shift = {
x: 0,
y: 0
}
startMove(e) {
const position = getPosition(e.currentTarget);
this.shift = {
x: e.pageX - position.left,
y: e.pageY - position.top
}
this.moving = true
}
move(event: MouseEvent) {
if (this.moving) {
this.elRef.nativeElement.parentElement.style.top = (event.clientY - this.shift.y) + 'px'
this.elRef.nativeElement.parentElement.style.left = (event.clientX - this.shift.x) + 'px'
}
}
function getPosition(elem) {
const box = elem.getBoundingClientRect();
return {
top: box.top + pageYOffset,
left: box.left + pageXOffset
};
}
Upvotes: 3