TypeScript https://omedia.dev/ en Unleashing the Power of SVG Templates in Angular https://omedia.dev/blog/unleashing-power-svg-templates-angular <span>Unleashing the Power of SVG Templates in Angular</span> <span><span lang="" about="/user/57" typeof="schema:Person" property="schema:name" datatype="">Besarion Satno</span></span> <span>Fri, 01/20/2023 - 16:34</span> <div class="f-body om-text-content"> <p>As a developer, you are probably familiar with the importance of creating high-quality and visually appealing graphics for your web applications. One of the best ways to achieve this is by using SVG. SVG is a powerful and versatile format that allows you to create and display vector graphics on the web. Since SVG is using XML to describe the graphics, generating dynamic SVGs can be quite useful and simple to implement.</p> <p>Apparently, in Angular, we can use <code>.svg</code> templates for components instead of the <code>.html</code> files we are accustomed to. This opens the door to using Angular's syntactic sugar, like property binding and event binding, with SVGs to create dynamic and interactive graphic content.</p> <p>In this blog post, we will take a closer look at how and why to use SVG templates in Angular. Using SVG templates in Angular allows you to create dynamic, responsive graphics that can change based on dynamic data or user interactions. This makes it a great choice for creating complex graphics, visualizations, and animations for your web applications.</p> <p>To use SVG as an Angular template, first, you need an Angular project. Let’s create one:</p> <pre> <code class="language-bash">ng new svg-demo</code></pre> <p>Now delete everything from <code>app.component.html</code> and create a new component:</p> <pre> <code class="language-bash">ng g c svg-demo</code></pre> <p>I usually manually change the <code>.html</code> suffix to <code>.svg</code>, delete its contents, and update the <code>.ts</code> file with the new <code>templateUrl</code> in the <code>@Component</code> decorator.</p> <pre> <code class="language-javascript">import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-svg-demo', templateUrl: './svg-demo.component.svg', styleUrls: ['./svg-demo.component.scss'], }) export class SvgDemoComponent implements OnInit { constructor() {} ngOnInit(): void {} }</code></pre> <p>CLI takes care of many things, such as adding component declarations to the <code>@NgModule</code> decorator, and we can freely use it in the <code>app.component.html</code> with the <code>&lt;app-svg-demo&gt;&lt;/app-svg-demo&gt;</code> tag.</p> <p>In <code>svg-demo.component.svg</code> I’ll paste the SVG code for this isometric cube which I got from <a href="https://draw.io/" target="_blank">draw.io</a></p> <img alt="Cube" data-entity-type="file" data-entity-uuid="c9769ad9-64eb-4139-a1fb-f4323cfb8c4e" src="/sites/default/files/inline-images/cube_0.png" class="align-center" width="1200" height="602" loading="lazy" /> <p>Here is the contents of <code>cube.svg</code>:</p> <pre> <code class="language-xml">&lt;svg width="91px" height="101px" viewBox="-0.5 -0.5 91 101" style="background-color: rgb(255, 255, 255);"&gt; &lt;g&gt; &lt;path d="M 45 0 L 90 21.61 L 90 78.39 L 45 100 L 0 78.39 L 0 21.61 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)"/&gt; &lt;path d="M 0 21.61 L 45 43.21 L 90 21.61 M 45 43.21 L 45 100" fill="none" stroke="rgb(0, 0, 0)"/&gt; &lt;/g&gt; &lt;/svg&gt;</code></pre> <p>To break this code down, the surrounding <code>&lt;svg&gt;</code> tag contains one <code>&lt;g&gt;</code> tag, which stands for "group" and groups two <code>&lt;path&gt;</code> tags. Those two paths represent these two parts of our shape:</p> <img alt="Paths comprising the cube" data-entity-type="file" data-entity-uuid="36634f36-41b3-40ed-86b2-6e1a68e9f7d0" src="/sites/default/files/inline-images/cube-parts.png" class="align-center" width="1200" height="562" loading="lazy" /> <p>Now, let's make this markup more Angular<em>-y</em> and decide what we want to do with it.</p> <p>One of the most basic things we can do in Angular Components is to bind <code>.html</code> and <code>.ts</code> files. In our case, we can bind values from <code>.ts</code> to the values in <code>.svg</code>. Let's consider the colors of those shapes and make the outline of the same color.</p> <p>Below, we have just an array of <code>colors</code>, a <code>strokeColor</code> property, and a function that returns a randomly chosen color from the array.</p> <pre> <code class="language-javascript">import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-svg-demo', templateUrl: './svg-demo.component.svg', styleUrls: ['./svg-demo.component.scss'], }) export class SvgDemoComponent implements OnInit { colors: string[] = [ 'red', 'orange', 'yellow', 'green', 'blue', 'purple', 'black', ]; strokeColor: string | undefined; constructor() {} getRandomColor() { const randomNumber = Math.floor(Math.random() * this.colors.length); return this.colors[randomNumber]; } ngOnInit(): void { this.strokeColor = this.getRandomColor(); } }</code></pre> <p>I want to bind the randomly chosen color to the stroke attribute of those two shapes, as we want to change both of them.</p> <pre> <code class="language-xml">&lt;svg width="91px" height="101px" viewBox="-0.5 -0.5 91 101" style="background-color: rgb(255, 255, 255);"&gt; &lt;g&gt; &lt;path d="M 45 0 L 90 21.61 L 90 78.39 L 45 100 L 0 78.39 L 0 21.61 Z" fill="rgb(255, 255, 255)" [attr.stroke]="strokeColor" /&gt; &lt;path d="M 0 21.61 L 45 43.21 L 90 21.61 M 45 43.21 L 45 100" fill="none" [attr.stroke]="strokeColor" /&gt; &lt;/g&gt; &lt;/svg&gt;</code></pre> <p>Now the color of the stroke changes on every refresh. Let's also change the fill color of the first <code>&lt;path&gt;</code> tag. I will modify the <code>.ts</code> file for my purposes:</p> <pre> <code class="language-javascript">strokeColor: string | undefined; // add new property fillColor: string | undefined; ngOnInit(): void { this.strokeColor = this.getRandomColor(); // assign random color to this as well this.fillColor = this.getRandomColor(); }</code></pre> <p>And I'll update the SVG template as well:</p> <pre> <code class="language-xml">&lt;svg width="91px" height="101px" viewBox="-0.5 -0.5 91 101" style="background-color: rgb(255, 255, 255);"&gt; &lt;g&gt; &lt;path d="M 45 0 L 90 21.61 L 90 78.39 L 45 100 L 0 78.39 L 0 21.61 Z" [attr.fill]="fillColor" [attr.stroke]="strokeColor" /&gt; &lt;path d="M 0 21.61 L 45 43.21 L 90 21.61 M 45 43.21 L 45 100" [attr.fill]="fillColor" [attr.stroke]="strokeColor" /&gt; &lt;/g&gt; &lt;/svg&gt;</code></pre> <p>Here are the results of multiple reloads, all with different (random) colors:</p> <img alt="Color variants" data-entity-type="file" data-entity-uuid="3aef3718-7507-4126-abc2-96d15d8f1576" src="/sites/default/files/inline-images/variants.png" class="align-center" width="3000" height="1571" loading="lazy" /> <p>Let's change the behavior and make the colors change on mouse click, instead of relying on the refresh button. We can use Angular's event binding syntax for this. I will modify the files once again:</p> <pre> <code class="language-javascript">changeColors() { this.strokeColor = this.getRandomColor(); this.fillColor = this.getRandomColor(); } ngOnInit(): void { this.changeColors(); }</code></pre> <pre> <code class="language-xml">&lt;svg width="91px" height="101px" viewBox="-0.5 -0.5 91 101" style="background-color: rgb(255, 255, 255);"&gt; &lt;g (click)="changeColors()"&gt; &lt;path d="M 45 0 L 90 21.61 L 90 78.39 L 45 100 L 0 78.39 L 0 21.61 Z" [attr.fill]="fillColor" [attr.stroke]="strokeColor" /&gt; &lt;path d="M 0 21.61 L 45 43.21 L 90 21.61 M 45 43.21 L 45 100" [attr.fill]="fillColor" [attr.stroke]="strokeColor" /&gt; &lt;/g&gt; &lt;/svg&gt;</code></pre> <p>That's basically it. If you want to move the shape, you can use the <code>x</code> and <code>y</code> attributes for each element, or for all of them at once with the parent tag.</p> <h4>Few things to keep in mind:</h4> <p><strong>1. </strong>When Angular renders the SVG Component in the app-root, it is usually rendered inside a &lt;div&gt;-like element. And if you want to nest multiple SVG components, you should change the component selector and use the directive's syntax like this:</p> <pre> <code class="language-javascript">@Component({ selector: '[app-svg-demo]', ... });</code></pre> <pre> <code class="language-xml">&lt;svg app-svg-demo&gt;&lt;/svg&gt;</code></pre> <p><strong>2. </strong>We need to use <code>[attr.X]</code> syntax to not get the following error:</p> <pre> <code>error NG8002: Can't bind to 'fill' since it isn't a known property of ':svg:path'.</code></pre> <p>According to Angular documentation:</p> <blockquote> <p>This error arises when attempting to bind to a property that does not exist. Any property binding must correspond to either:</p> <ul> <li>A native property on the HTML element, or</li> <li>An <code>@Input()</code> property of a component or directive applied to the element.</li> </ul> </blockquote> <p>So by using <code>[attr.X]</code> syntax, we make sure that Angular knows that we’re trying to bind the value to the native property on the HTML (in our case SVG) element.</p> <p><strong>3. </strong>We can as easily use the <code>@Input</code> and <code>@Output</code> data / event binding between a parent and child components.</p> <p><strong>4. </strong>We can also use structural directives <code>*ngIf</code> / <code>*ngFor</code> with the SVG elements (don’t forget to add <code>CommonModule</code> to the <code>AppModule</code> imports).</p> <p><strong>5. </strong>SVG paths and shapes follow their order. The next element will be drawn on top of the previous one.</p> </div> <div> <div><a href="/topic/angular" class="e-tag-logo-tag"> <img src="/sites/default/files/2021-02/angular.svg" alt="Angular" loading="lazy" width="16" height="16" /> Angular </a> </div> <div><a href="/topic/svg" class="e-tag-logo-tag"> SVG </a> </div> <div><a href="/topic/javascript" class="e-tag-logo-tag"> <img src="/sites/default/files/2021-02/javascript.svg" alt="JavaScript" loading="lazy" width="16" height="16" /> JavaScript </a> </div> <div><a href="/topic/typescript" class="e-tag-logo-tag"> TypeScript </a> </div> </div> <div> <img src="/sites/default/files/styles/blog_full/public/2023-01/angular-svg.png?itok=EkRMKKje" width="1360" height="714" alt="" loading="lazy" typeof="foaf:Image" /> </div> <div id="field-language-display"><div class="js-form-item form-item js-form-type-item form-item- js-form-item-"> <label>Language</label> English </div> </div> Fri, 20 Jan 2023 12:34:45 +0000 Besarion Satno 82 at https://omedia.dev