In the first article of the “Drag. Drop. Engage.” series, we laid the groundwork by Creating a Basic Draggable Directive in Angular. This foundational piece enabled users to move elements around the page, setting the stage for a more dynamic and customisable user experience. However, for a truly interactive and organised dashboard, simply being able to drag elements isn’t enough.
We’ll take things further in this article by building a visual grid layout. This grid will act as the structured space where draggable elements can be positioned, providing visual guidance and functional structure. By the end of this article, you’ll clearly understand how to create an SVG grid, setting the stage for more complex interactions and precision placement in your Angular dashboard.
To begin, we create the grid directive, which we will use to render the grid.
ng g directive grid
There are many different ways in which we can visualise a grid. In this example, we use SVG patterns. The SVG is constructed in four steps:
- We draw a
path
from the lower left corner to the upper left corner, continue to the upper right corner. This gives the grid line. We create a
pattern
element and add thepath
to it.We create a
rect
and set thepattern
as it’s filling.Lastly, we create the
SVG
and add therect
andpattern
to it
/**
* Creates an SVG element with a grid pattern
* @param cellSize Size of the grid cells in pixels
* @param borderColor CSS color of the grid border
* @param borderWidth Width of the grid border in pixels
* @returns SVG element with a grid pattern
*/
private createGrid(cellSize: number, borderColor: string = '#c2c2c2', borderWidth: number = 1): SVGElement {
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); // Create a path element
path.setAttribute('d', `M 0 ${cellSize} L 0 0 ${cellSize} 0`); // Set the path data
path.setAttribute('fill', 'none'); // Set the fill to none
path.setAttribute('stroke', borderColor); // Set the stroke color
path.setAttribute('stroke-width', borderWidth.toString()); // Set the stroke width
let pattern = document.createElementNS('http://www.w3.org/2000/svg', 'pattern'); // Create a pattern element
pattern.setAttribute('id', 'grid'); // Set the id to 'grid'
pattern.setAttribute('width', cellSize.toString()); // Set the width to the size
pattern.setAttribute('height', cellSize.toString()); // Set the height to the size
pattern.setAttribute('patternUnits', 'userSpaceOnUse'); // Set the pattern units to 'userSpaceOnUse'
pattern.appendChild(path); // Append the path to the pattern
let grid = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); // Create a rect element
grid.setAttribute('width', '100%'); // Set the width to 100%
grid.setAttribute('height', '100%'); // Set the height to 100%
grid.setAttribute('fill', 'url(#grid)'); // Set the fill to the grid pattern
let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); // Create an SVG element
svg.setAttribute('width', '100%'); // Set the width
svg.setAttribute('height', '100%'); // Set the height
svg.appendChild(pattern); // Append the pattern to the SVG
svg.appendChild(grid); // Append the grid to the SVG
return svg; // Return the SVG element
}
With the functionality out of the way, we can initialise the directive. We are using the ngAfterViewInit
lifecycle hook to ensure our HTML element is ready for use. We also introduce three @Input
parameters with default values to make the functionality reusable.
@Input('cellSize') cellSize: number = 50; // Size of the grid cells in pixels
@Input('borderColor') borderColor: string = '#c2c2c2'; // CSS color of the grid border
@Input('borderWidth') borderWidth: number = 1; // Width of the grid border in
/**
* AfterViewInit lifecycle hook
* Creates a grid with the specified cell size and appends it to the container
*/
public ngAfterViewInit(): void {
let grid = this.createGrid(this.cellSize, this.borderColor, this.borderWidth); // Create a grid with
this._container.nativeElement.appendChild(grid); // Append the grid to the container
}
To render the grid, we add the grid
directive to our HTML. I have added a main
element and attached ngGrid
to it.
💡 I added a Bootstrap header to the page to make it more visually appealing. It also gives us an area where dropping isn’t allowed. The custom CSS can be found in below StackBlitz.
<div class="page-grid">
<nav class="navbar bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="#">
<img src="angular.svg" alt="Angular Drag and Drop" width="30" height="24" class="d-inline-block align-text-top">
Angular Drag and Drop
</a>
</div>
</nav>
<main ngGrid>
<div class="card col-2 position-absolute" ngDraggable>
<div class="card-header">
Drag and Drop
</div>
<div class="card-body">
</div>
</div>
</main>
</div>
And here is the result. The draggable card from part one and the grid we just created.
With your visual grid now in place, you've added a critical layer of structure to your drag-and-drop dashboard, making it easier for users to position elements accurately and maintain a well-organised layout.
We'll introduce grid-snapping and restrictions in the next article of the “Drag. Drop. Engage.” series. This enhancement will ensure that interactions remain intuitive and controlled, providing users with a seamless and polished experience. Stay tuned as we continue to refine and enhance your Angular drag-and-drop dashboard!
Top comments (1)
Very cool, thanks for sharing