sanakkian
sanakkian

Reputation: 299

How to make Bootstrap modal popup as movable in Angular2

I have a Bootstrap modal popup in my Angular2 application. I want it to be draggable. It will be so helpful if someone can help me to resolve this issue.

<div class="modal modal-sm fade fade in" [class]="modalWorkPhone" id="myModal" role="dialog">
   <div class="modal-dialog"> 
   <!-- Modal content-->
     <div class="modal-content">
       <div class="panel-heading" style="background-color:#2e90bd">
       </div>
     </div>
   </div>
</div>

Upvotes: 4

Views: 7650

Answers (3)

michal.jakubeczy
michal.jakubeczy

Reputation: 9479

I got inspired by this approach found on https://gist.github.com/markleusink/7af171d5f17e7dc9714e69965fdabab9

I slightly modified it and here's the result:

Use it by adding draggable to your modal header <div class="modal-header" draggable> </div>

import { Directive, ElementRef, HostListener, AfterViewInit } from '@angular/core';

/*
 * Directive to add 'drag' support to Ngx Bootstrap modals (https://github.com/valor-software/ngx-bootstrap).
 * Based on this library to enable drag support for an ng-bootstrap modal: https://github.com/mattxu-zz/ngb-modal-draggable
 *
 * Enable by adding the directive to the modal-header element, e.g.:
 * <div class="modal-header" draggable>  </div>
 */

@Directive({
    selector: '[draggable]'
})

export class DraggableDirective implements AfterViewInit {
    private modalElement: HTMLElement;
    private topStart: number;
    private leftStart: number;
    private isDraggable: boolean;
    private handleElement: HTMLElement;

    constructor(public element: ElementRef) {}

    public ngAfterViewInit() {
        let element = this.element.nativeElement;

        //only make the modal header draggable
        this.handleElement = this.element.nativeElement;

        //change cursor on the header
        this.handleElement.style.cursor = 'move';

        //get the modal parent container element: that's the element we're going to move around
        for (let level = 3; level > 0; level--) {
            element = element.parentNode;
        }

        this.modalElement = element;
        this.modalElement.style.position = 'relative';
    }

    @HostListener('mousedown', ['$event'])
    public onMouseDown(event: MouseEvent) {
        if (event.button === 2 || !this.handleElement) {
            return; // prevents right click drag or initialized handleElement
        }

        if (event.target !== this.handleElement && !this.searchParentNode(<any>event.target, this.handleElement)) {
            return; // prevents dragging of other elements than children of handleElement
        }

        //enable dragging
        this.isDraggable = true;

        //store original position
        this.topStart = event.clientY - Number(this.modalElement.style.top.replace('px', ''));
        this.leftStart = event.clientX - Number(this.modalElement.style.left.replace('px', ''));
        event.preventDefault();
    }

    @HostListener('document:mouseup', ['$event'])
    public onMouseUp(event: MouseEvent) {
        this.isDraggable = false;
    }

    @HostListener('document:mousemove', ['$event'])
    public onMouseMove(event: MouseEvent) {
        if (this.isDraggable) {
            //on moving the mouse, reposition the modal
            this.modalElement.style.top = (event.clientY - this.topStart) + 'px';
            this.modalElement.style.left = (event.clientX - this.leftStart) + 'px';
        }
    }

    @HostListener('document:mouseleave', ['$event'])
    public onMouseLeave(event: MouseEvent) {
        this.isDraggable = false;
    }

    private searchParentNode(element: Node, tag: Node): Node {
        while (element.parentNode) {
            element = element.parentNode;
            if (element === tag) {
                return element;
            }
        }

        return null;
    }

}

Upvotes: 1

Vadim Loboda
Vadim Loboda

Reputation: 3111

The following directive code is for Angular 6+ and the modal window from ng-bootstrap library.

You have to make this directive visible for your view modules and all the modals will become draggable without any changes, because the directive binds to the .modal-header class:

import { Directive, ElementRef, Renderer2, AfterViewInit, Input, OnDestroy } from '@angular/core';
import { fromEvent, Subscription } from 'rxjs';
import { mergeMap, takeUntil, tap } from 'rxjs/operators';

interface Point {x: number, y: number};

@Directive({
  selector: '.modal-header'
})
export class DragModalDirective implements AfterViewInit, OnDestroy  {

  constructor (
    private el: ElementRef,
    private renderer: Renderer2,
  ) {}

  subscription: Subscription;

  start: Point;
  offset: Point = {x: 0, y: 0};

  ngAfterViewInit() {       

    setTimeout(() => {
      this.makeItDraggable();      
    });

  }  

  private makeItDraggable() {

    const modalDialogElement = this.el.nativeElement.closest(".modal-dialog");

    if (!modalDialogElement) {
      console.error('DragModalDirective cannot find the parent element with class modal-dialog')
      return;
    }

    this.renderer.setStyle(this.el.nativeElement, 'user-select', 'none');
    this.renderer.setStyle(this.el.nativeElement, 'cursor', 'move'); 

    this.renderer.setStyle(modalDialogElement, 'transition', 'none');

    const down$ = fromEvent(this.el.nativeElement, 'mousedown')
    const move$ = fromEvent(document, 'mousemove');
    const up$ = fromEvent(document, 'mouseup')

    const drag$ = down$.pipe(        
      tap(($event: MouseEvent) => {
        this.start = {
          x: $event.clientX - this.offset.x, 
          y: $event.clientY - this.offset.y
        };
      }),
      mergeMap(down => move$.pipe(
        takeUntil(up$)
      ))
    );

    this.subscription = drag$.subscribe(($event: MouseEvent) => {
      this.offset = {
        x: $event.clientX - this.start.x,
        y: $event.clientY - this.start.y
      }

     this.renderer.setStyle(modalDialogElement, 'transform', `translate(${this.offset.x}px, ${this.offset.y}px)`);
    }) 
  }

  ngOnDestroy() {
    if (this.subscription)
      this.subscription.unsubscribe();
  } 
}

Upvotes: 6

Arno
Arno

Reputation: 336

I had the same user request and solved it by using npm module "angular-draggable". Just install as described and add the draggable="true" attribute to your modal div.

Upvotes: 0

Related Questions