Adjust the height of a div vertically in Angular 2+

Recently, I started using angular2 and I've been attempting to create a vertically resizable div without success. I have experimented with a directive for this purpose.

Below is the code for my directive:

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

@Directive({
  selector: '[appNgxResizer]'
})
export class NgxResizerDirective {

  constructor(private el: ElementRef) {
  }

  @HostListener('mousemove', ['$event']) resize(e) {
    this.el.nativeElement.parentNode.style.height = (e.clientY - this.el.nativeElement.parentNode.offsetTop) + 'px';
    event.preventDefault();
  }

  @HostListener('mouseup', ['$event']) stopResize(e) {
    event.preventDefault();
  }
}

You can view what I have attempted on StackBlitz here.

I am aiming to achieve a div that is resizable when clicked and dragged, similar to this example here.

Thank you,

Answer №1

It seems that you overlooked the part where maintaining the old height value is crucial, alongside checking the state during mouseup and listening for mousedown events. Although not demonstrated as a directive in the example below, incorporating it would add complexity.

Check out this Stackblitz example

Typescript:

  height = 150;
  y = 100;
  oldY = 0;
  grabber = false;

  @HostListener('document:mousemove', ['$event'])
  onMouseMove(event: MouseEvent) {
    if (!this.grabber) {
        return;
    }
    this.resizer(event.clientY - this.oldY);
    this.oldY = event.clientY;
  }

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

  resizer(offsetY: number) {
    this.height += offsetY;
  }


  @HostListener('document:mousedown', ['$event'])
  onMouseDown(event: MouseEvent) {
    this.grabber = true;
    this.oldY = event.clientY;
    event.preventDefault();
  }

HTML

<div class="textarea" [style.height.px]='height' contenteditable="true" >
  this is a text area
  <div class="grabber"></div>  
</div>

Answer №2

Utilizing @Vega's solution - a custom directive.

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

@Directive({
  selector: '[resizer]'
})
export class NgxResizerDirective implements OnInit {

  height: number;
  oldY = 0;
  grabber = false;

  constructor(private el: ElementRef) { }

  @HostListener('document:mousemove', ['$event'])
  onMouseMove(event: MouseEvent) {

    if (!this.grabber) {
      return;
    }

    this.resizer(event.clientY - this.oldY);
    this.oldY = event.clientY;
  }

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

  resizer(offsetY: number) {
    this.height += offsetY;
    this.el.nativeElement.parentNode.style.height = this.height + "px";
  }

  @HostListener('mousedown', ['$event']) onResize(event: MouseEvent, resizer?: Function) {
    this.grabber = true;
    this.oldY = event.clientY;
    event.preventDefault();
  }

  ngOnInit() {
    this.height = parseInt(this.el.nativeElement.parentNode.offsetHeight);
  }

}

HTML

<div class="textarea" contenteditable="true">
  this is a text area
  <div class="grabber" resizer contenteditable="false" ></div>
</div>

Answer №3

Give this a try:

Introduce a new variable:

private resizable = false;

When the mouse is pressed down, activate resizing by setting resizable to true:

@HostListener('mousedown', ['$event']) toggleResize(e) {
    this.resizable = true;
    event.preventDefault();
}

This way, you can only resize when the mouse button is held down:

@HostListener('window:mousemove', ['$event']) performResize(e) {
  if (this.resizable) {
    this.el.nativeElement.parentNode.style.height = (e.clientY - this.el.nativeElement.parentNode.offsetTop) + 'px';
  }
  event.preventDefault();
}

When the mouse button is released, set resizable back to false to stop resizing:

@HostListener('window:mouseup', ['$event']) endResize(e) {
    this.resizable = false;
    event.preventDefault();
}

Also, consider checking out this resource

(UPDATE: created stackblitz example)

Answer №4

This amazing library called angular-split has been a game changer for me in terms of ease and functionality. You can check it out here.

Below is an example of how I have used it in an app component, with a navigation menu on the left and different components/pages being rendered on the right. This use case is quite popular especially if you prefer a vertical navigation menu.

<as-split direction="horizontal" style="height: 1000px;">
        <as-split-area size="15">
            <app-nav-menu></app-nav-menu>
        </as-split-area>
        <as-split-area size="85">
            <div class="col-12 col-lg-9 body-content">
                <router-outlet></router-outlet>
            </div>
        </as-split-area>
</as-split>

Answer №5

I have made some enhancements to the responses from @Sibiraj and @Vega. One of the key concerns with the proposed solution is related to performance:

@HostListener('document:mousemove', ['$event'])
  onMouseMove(event: MouseEvent) {

    if (!this.grabber) {
      return;
    }

    this.resizer(event.clientY - this.oldY);
    this.oldY = event.clientY;
  }

It's important to note that

@HostListener('document:mousemove', ['$event'])
will be triggered on every mouse movement in your document, which can impact performance. To address this, you can add a mousemove listener after the mousedown event and remove it after the 'mouseup' event. Here is an updated version based on my approach:

import {Directive, ElementRef, HostListener, OnDestroy, OnInit} from '@angular/core';
import {fromEvent, Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';

@Directive({
  selector: '[resizer]'
})
export class ResizeDirective implements OnInit, OnDestroy {
    height: number;
    oldY = 0;
    grabber = false;
    destroy$ = new Subject();

    constructor(private el: ElementRef) { }

    ngOnInit() {
        this.height = parseInt(this.el.nativeElement.parentNode.offsetHeight, 10);
    }

    @HostListener('document:mouseup', ['$event'])
    onMouseUp(): void {
        this.grabber = false;
        this.destroy$.next();
    }

    @HostListener('mousedown', ['$event']) onResize(event: MouseEvent, resizerCallback?: Function) {
        this.grabber = true;
        this.oldY = event.clientY;
        event.preventDefault();

        this.addMouseMoveListener();
    }

    resizer(offsetY: number): void {
        this.height += offsetY;
        this.el.nativeElement.parentNode.style.height = this.height + 'px';
    }

    addMouseMoveListener(): void {
        fromEvent(document, 'mousemove')
            .pipe(takeUntil(this.destroy$))
            .subscribe(this.mouseMoveCallback.bind(this));
    }

    mouseMoveCallback(event: MouseEvent): void {
        if (!this.grabber) {
            return;
        }

        this.resizer(event.clientY - this.oldY);
        this.oldY = event.clientY;
    }

    ngOnDestroy() {
        this.destroy$.next();
    }

}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I provided Array.Filter with a function instead of a predicate, and surprisingly it gave back the entire array. How is that possible?

I encountered an unusual scenario where I passed a function instead of a predicate to Array.filter. This function modified individual student objects and the filter returned the whole array. This led me to question, why is this happening? According to co ...

Accessing Google Feed API to retrieve media thumbnails

I am currently utilizing the Google Feed API to extract a thumbnail from an RSS feed ("media:thumbnail") The media:thumbnail element in the RSS feed is structured as follows: <media:thumbnail url="http://anyurl.com/thumbnailname.jpg" width="150" heigh ...

Quickest method for skimming through an extremely lengthy document beginning at any specified line X

In my current project, there is a text file that is written to by a python program and read by another program to display on a web browser. JavaScript handles the reading process at the moment, but I am considering moving this functionality to python. The ...

Extract images from dynamically loaded JavaScript website

I'm attempting to extract images from a webpage that is rendered using JS, but the picture links in the source code are incomplete. Here is where the images are located: <script language="javascript" type="text/javascript"> </script> < ...

Troubleshooting Jquery Ajax Failure in Laravel 4

Whenever I utilize jQuery Ajax to insert data into the database, a peculiar issue arises. Upon clicking the submit button, my page mysteriously returns blank. To shed light on this dilemma, I decided to employ Firebug for debugging purposes, only to stumbl ...

Angular - optimizing performance with efficient HTTP response caching tactics

I'm managing numerous services that make requests to a REST service, and I'm looking for the optimal method to cache the data obtained from the server for future use. Can someone advise on the most effective way to store response data? ...

How to implement form modal binding using Laravel and Vue.js

There are two models named Tour.php public function Itinerary() { return $this->hasMany('App\Itinerary', 'tour_id'); } and Itinerary.php public function tour() { return $this->belongsTo('App\Tour', ...

Input information into a JSON container

I've been struggling to find a solution for this issue. Here's the JSON variable I'm working with, which includes the names "rocky" and "jhon": var names = [ "rocky", "jhon" ]; Now, I need to add a new val ...

How can you send an error message from Flask and then process it in JavaScript following an AJAX request?

So I have successfully created a Python backend using Flask: @app.route('/test/') def test(): data = get_database_data(request.args.id) # Returns dict of data, or None if data is None: # What should be the next step here? re ...

Exploring the Enzyme library in React to trigger an onClick event with a specific parameter

In my quest to simulate an onClick method in my unit tests using Enzyme for React, I have encountered various tutorials on simulating an onClick event that takes an event e. For example: handleClick(e) { // Does something } .... <MyComponent onCli ...

What is the best method for transitioning to a new page in React Native using Ignite Bowser?

Recently I ventured into the world of React Native with Ignite Bowser. My current project involves building a React Native app using Ignite Bowser. At the start of my app, there's a welcoming screen that pops up. It features a 'continue' bu ...

Achieving dynamic key assignment when updating with mongoose in NodeJS and Express

With a multitude of keys requiring updates from a single function, I am seeking guidance on how to dynamically set the key for updating. static async updateProfile(req, res, next) { const userId = req.body.userId; // The key requiring an update ...

Error Message: An issue has occurred with the server. The resolver function is not working properly in conjunction with the next

https://i.stack.imgur.com/9vt70.jpg Encountering an error when trying to access my login page. Using the t3 stack with next auth and here is my [...nextauth].ts file export const authOptions: NextAuthOptions = { // Include user.id on session callbacks ...

Angular - A simple way to conceal a specific row in a mat-table using Angular

I am looking to dynamically hide or show a specific column in a table by clicking on a button. The goal is to hide or delete the weight column when the "hide weight" button is clicked, and then show the weight column when the "show weight" button is clicke ...

Remove attributes from a collection of objects

Here is an array of objects: let array = [{ firstName: "John", lastName : "Doe", id:5566, weight: 70 },{ firstName: "Francis", lastName : "Max", id:5567, weight: 85 }]; I am looking to remove the properties "lastName" and "weight" for all obj ...

"Troubleshooting: State array in ReactJS/NextJS not rendering correctly following setState

I am facing challenges with rendering my objects using .map() within React / NextJS. Within my code, I have a function where I retrieve images from Firebase Cloud Storage as shown below: getImages = () => { let firebase = loadFirebase() ...

Guide to building a nested React component

My custom dropdown component requires 2 props: trigger (to activate the dropdown) list (content to display in the dropdown) Below is the implementation of my component: import { useLayer } from "react-laag"; import { ReactElement, useState } fr ...

Having trouble getting Vue.js hello world to display on the page

I am attempting to create a Hello World app following the Vue.js site's get started documentation. Everything seems to be in order, but only the HTML code is being displayed on the page. Vue version: 1.0.26 Below is the HTML code: <!DOCTYPE ht ...

What is the correct way to define the field name in the update() function of Mongoose?

When attempting to update every field contained in the dataToChange object, I encountered a problem where the update() method does not take the key name from the outside. Instead, it looks for a "key" field within the database's object. How can I work ...

What is the best way to incorporate both static text and dynamic variables within a mat tooltip in Angular?

Is it possible to combine static text and dynamic variables in a mat tooltip using Angular? <span class="trim" [matTooltip]="Updated at test.updated by test.updated_at.name" > {{test.created_by.email}}</span> Any assis ...