All Articles

Working With Angular and Dynamic SVGs - 1 - Component Relationships

Text Edit Cover

Photo by Clem Onojeghuo on Unsplash

Table of Contents

Intro

The SVG Rendering Context

Setting up Angular

Creating New Components

Modify SVG Parent Component

Modify SVG Child Component

Adding Child to Parent

Intro

It’s easy to pop an image tag into a web page and point it to an SVG file. It loads and you can resize it no problem, which is great. Sometimes that isn’t enough. What happens if you want to create an interactive graph with Angular using dynamic data? That isn’t just an image tag. This series will show how to use dynamic SVGs with angular components. By the end of this series, we will have a set of angular components to make a dynamic, interactive SVG plot.

The SVG Rendering Context

Before we jump right in we should talk about SVG. SVG stands for Scalable Vector Graphics. Most pictures we see are raster graphics. Raster graphics are created from an array of pixels, each having a set of 8-bit RGB values to define the color (thus 0-255). Since there is a finite number of pixels in the array, if we zoom in far enough on the picture, we will start to see pixelation. Not awesome for high quality sites. On the other hand, SVGs are an implementation of vector graphics. Vector graphics are defined by points, lines, and curves. Since all elements of an SVG are mathematically defined, SVGs have the ability to scale as big or small as they need to to fit the space. All elements are calculated, and therefore will not pixelate. How neat!

Setting up Angular

We are going to start with a blank project. If you haven’t already, install the Angular CLI with:

npm install -g @angular/cli

If you run ng --version it will tell you the version of angular you are using. At this time, the version I am using is:

Angular CLI: 9.1.4
Node: 12.13.1
OS: win32 x64

Angular:
...
Ivy Workspace:

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.901.4
@angular-devkit/core         9.1.4
@angular-devkit/schematics   9.1.4
@schematics/angular          9.1.4
@schematics/update           0.901.4
rxjs                         6.5.4

Navigate to the parent folder for your project and run:

ng new angularSVG

Go ahead and enter n for adding routing and SCSS for the stylesheet format. After installation, launch angular with your editor of choice and start a development server using either npm start or ng serve. If you’ve never used Angular before, I suggest going to check out a couple of tutorials before continuing with this series, as we will not be discussing basics.

Creating New Components

At this point we have a fresh Angular project open. We have a development server running with live reloading. For now, let’s create two components. The first will be the plot container. This component will be in charge of the SVG element. The second component will be in charge of the actual plot area. I will run

ng g component line-plot

and

ng g component line-plot/plot-area

to create these components.

Modify SVG Parent Component

The first thing we’ll do is clear out the app.component.html file since we don’t need the boilerplate that ships with Angular. We’ll then add the LinePlotComponent to the AppComponent within a div. I’m adding the div in there so we can control the parent size. I want to be able to drop this component into whatever size parent and have it resize itself. In a real application, the plot component probably wouldn’t be at the top level like this and would have a parent controlling it’s size, so we will build it like that. Our app.component.html should look like this now:

line-plot.component.html

<div style="width: 800px; height: 600px">
  <app-line-plot></app-line-plot>
</div>

Now let’s modify our line-plot template. First, we’ll clear everything in the line-plot.component.html and add an svg element to the template. I’m going to set the width and height attributes of the svg element to 100% and set a nice aqua background to make sure it is actually rendering to the screen.

This is what we have now in our line-plot.component.html.

line-plot.component.html

<svg width="100%" height="100%" style="background-color: aqua;"></svg>

When you save, you should see an aqua square (the svg element) rendered to the page. Since the line-plot component is inheriting the size from the parent div (currently 800px by 600px) all we need to do to change the size of the svg element is to change the size of the parent div for the line plot.

For now, let’s add a blue rectangle as a child of the svg element. Our line-plot.component.html should look like this now:

line-plot.component.html

<svg width="100%" height="100%" style="background-color: aqua;">
  <rect
    width="300"
    height="100"
    style="fill: rgb(0, 0, 255); stroke-width: 3; stroke: rgb(0, 0, 0);"
  />
</svg>

Modify SVG Child Component

Right now I am not worried about breaking the styles away from the template, but eventually I will be. Right now I just want to make sure I have everything rendering. If we take a look at the webpage, we should see an aqua background with a dark blue square inside. We will use this square to ensure the child component is rendering, so cut it from line-plot.component.html and add it to the plot-area.component.html. This is what our two templates look like now:

line-plot.component.html

<svg width="100%" height="100%" style="background-color: aqua;"></svg>

plot-area.component.html

<rect
  width="300"
  height="100"
  style="fill: rgb(0, 0, 255); stroke-width: 3; stroke: rgb(0, 0, 0);"
/>

We save everything and … it crashes with the following error:

Failed to compile.

Errors parsing template: Only void and foreign elements can be self closed "rect" ("  [ERROR ->]<rect
    width="300"
    height="100"
"): <your project dir>/src/app/line-plot/plot-area/plot-area.component.html@0:2

This error took me a while to figure out at the start, but it’s fairly common when working with Dynamic SVGs in Angular. What is going on here? If we place the rect element as a child of an svg element it works fine, we just saw that. We broke it out to its own component and it doesn’t work. This is because Angular does not see that this component will be used within the context of an svg element. Right now we could just drop this anywhere in our app and ask Angular to run it. It looked, and said:

I don’t know what a <rect> element is, and you can’t self close it since I don’t know what it is.

We can fix this very easily. SVG elements have a “Namespace” (Forgive me if that is not the right terminology). If we tell the template that we are using the SVG Namespace specifically, this will fix the error. Let’s change the plot-area.component.html to the following:

plot-area.component.html

<svg:rect
  width="300"
  height="100"
  style="fill: rgb(0, 0, 255); stroke-width: 3; stroke: rgb(0, 0, 0);"
/>

The prefixed svg: says:

Look for this element with all the svg elements, not the HTML elements.

When we save, it compiles now.

Adding Child to Parent

Normally, adding a child to a parent is really easy, just drop the child selector in the parent and away you go. Since we are working with SVGs this gets slightly more complicated. We are not working in the HTML context anymore and the SVG context can’t add another element as easily as HTML can so we have to find a workaround. We can prove this by adding the app-plot-area selector to the line-plot.component.html like this:

line-plot.component.html

<svg width="100%" height="100%" style="background-color: aqua;">
  <app-plot-area></app-plot-area>
</svg>

When we save it… compiles? Yes, because from an HTML perspective, we didn’t actually do anything wrong. We can add an app-plot-area selector into an XML-like document right there. If we look at the web page, you will not see the dark blue rectangle still. From an HTML perspective everything is hunky dory. The SVG element doesn’t have any idea what to do with the app-plot-area element so it chooses not to render it. We still need this component to break up our parent component into manageable pieces, so how do we work around this? We need to use an element that SVG does understand: The g element. SVG Groups allow us to bring in another component. The final step is to tell the child component that it can attach to a different type of element than the plot-area one. We can do this by adding square braces around the selector, much like a directive.

plot-area.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: '[app-plot-area]',
  templateUrl: './plot-area.component.html',
  styleUrls: ['./plot-area.component.scss']
})
export class PlotAreaComponent implements OnInit {
  constructor() {}

  ngOnInit(): void {}
}

If you have ts-lint running it will probably yell at you for doing this. Since we know we are not making a mistake, we can disable the component selector rule by adding:

// tslint:disable-next-line: component-selector

to the line above the the selector.

Let’s modify the line-plot.component.ts to use this child component now:

line-plot.component.ts

<svg width="100%" height="100%" style="background-color: aqua;">
  <g app-plot-area></g>
</svg>

Save all files and voila!! You have a parent child relationship using SVGs! Hope you enjoyed this article. Next article we will work on some data binding nuances with SVGs and Angular.