Tag: SVGs

  • The right way to componentize SVGs for your Angular app

    The right way to componentize SVGs for your Angular app

    Last year, I wrote an article about how to create an SVG library in Angular by making components that link to SVG files instead of HTML templates. Shortly afterwards, my boss tasked me with finding a solution to the excessive DOM size problem, which we discovered was being caused by all of the elements that make up our SVGs.

    How to create references instead of actual clones to your SVGs

    After doing extensive research and trying different methods, I came across the ‘symbol’ and ‘use’ HTML elements. This article gives great in-depth explanations into why this is the correct approach.

    If you have ever worked in Flash…I mean Animate, you will remember how awesome symbols were. They enabled you to create one symbol and then reference that symbol so any changes you did to the original were cascaded to all of its references. By using ‘symbol’ and ‘use’, you are essentially doing the same thing.

    To make this solution even more enticing, the ‘use’ element acts as a container and stores its children in the shadow DOM. In other words, your SVG elements no longer count towards your DOM size.

    The drawbacks of the shadow DOM and how to overcome them

    There are some drawbacks though with using a closed shadow DOM. For example, you can’t access or manipulate the SVG’s viewBox through JavaScript. Therefore, you won’t be able to do things like pull out the SVG’s exact dimensions without inspecting the element through a browser or updating the paths of the SVG without an image editor.

    This can be problematic because if you don’t set the SVG’s width and height, it will automatically have a width of 300 pixels and a height of 200 pixels. If your SVG is considerably smaller, you will see a lot of blank space around the SVG. Because we’re going to create one component for all of your SVGs, you can’t set one universal viewBox to handle this problem.

    The viewBox isn’t the only thing that’s inaccessible. All of the SVG’s styles are as well. This is actually a pretty good benefit because a change to the original would be applied to all of its references.

    Don’t fret; we can easily overcome these hurdles by making our SVG generic and adaptable. We can use properties like ‘inherit’ on the SVG’s width and height and ‘currentColor’ for its fill and outline so they can be set through a parent or ancestor.

    How to create the SVG component

    Step 1: Create a graphic and export it as an SVG

    Note: In order to individually style your SVG and have it scale gracefully to any size, you must use actual paths instead of embedded images for your SVGs.

    For example, I made my logo with vector art using Illustrator. This is what it looks like when I export just the cloud as an SVG:

    <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 229 149.04">
      <path d="M189.37,142a12.35,12.35,0,0,1-3.2-3c-1.26-1.65-2.31-3.5-3.71-5a12.12,12.12,0,0,0,2.48,4.77A12.55,12.55,0,0,0,189.37,142Z" transform="translate(0 0)" style="fill:#ccc"/>
      <path d="M192.89,142.94a12.66,12.66,0,0,1-3.52-1A6.19,6.19,0,0,0,192.89,142.94Z" transform="translate(0 0)" style="fill:#ccc"/>
      <path d="M182.46,133.91a11.83,11.83,0,0,1-.4-3.65,5,5,0,0,0-1.2,2.21,1.51,1.51,0,0,1,.26.18A10.64,10.64,0,0,1,182.46,133.91Z" transform="translate(0 0)" style="fill:#ccc"/>
      <path d="M229,76.35a28.33,28.33,0,0,0-19-26.61,14.29,14.29,0,0,0,1.32-6,14.8,14.8,0,0,0-14.93-14.67h-.09a15.07,15.07,0,0,0-10.47,4.2,27.92,27.92,0,0,0,.45-5C186.28,12.67,173.36,0,157.43,0a28.94,28.94,0,0,0-25.81,15.64,24.52,24.52,0,0,0-2.7-.2A29.22,29.22,0,0,0,113.33,11C97.4,11,84.48,23.62,84.48,39.25a28.41,28.41,0,0,0,.15,2.92c-2.79-.57-5.76-1.72-8.51-2.21-4.42-.54-8.85,2.71-12.19,5.47a36.36,36.36,0,0,0-3.47,3.66A31.66,31.66,0,0,0,52.9,67.61c-.56,13,10,21.14,18.8,29.27,1.1,1.63,0,4.88,1.1,5.42,3.32,3.26,6.64-2.71,6.08-6-1.1-4.88-6.08-6-8.84-9.22-3-3.25-2.91-7.58-1-11.31a14.81,14.81,0,0,1,2.49-3.44c3.45-4.22,9-3.68,13.41-2.59,7.19,2.17,14.93,6,16.6,13.59-.06,1.62-.15,3.29-.28,5-1.06,13.49-4.32,26.82-15.67,35.58-8.37,6.46-19.16,9.14-29.75,10.3s-21.32.93-31.87,2.4c14.49-4.51,29.91-5.73,44.28-10.58,7.48-2.53,14.82-6.18,19.89-12.12a35.54,35.54,0,0,0,7.68-17.8c1.05-6.79.27-14.5-4.81-19.23-3.5-3.26-9.84-4.22-12.87-.53s-1.07,9,1.5,12.82A19.63,19.63,0,0,1,78,83.06a6.67,6.67,0,0,1,2.37-5.66c2.14-1.58,5.3-1.22,7.48.29a13.06,13.06,0,0,1,4.46,6.37,28.19,28.19,0,0,1-3,25.38C82.12,120,68.58,124.6,55.72,126.23,46.5,127.4,37.17,127.4,28,128.57S-12.06,144.48,5.1,148c16,3.23,88.05-1.41,106.45-9.34s34.35-20.07,54.15-24.38c5-1.09,9.4,0,13.82,2.71,7.19,4.34,6.08,11.92,6.08,18.43,0,2.17,2.21,4.88,4.43,4.88,7.74,0,13.82-7.59,14.37-14.64-.56,7.19-5.48,20.7-15.52,16.76-2.32-.91-4.12-2.73-5.85-4.5a6.9,6.9,0,0,0,2.39,3.76,13.57,13.57,0,0,1-2.71-2.53,9,9,0,0,1-2-4.36,6,6,0,0,1,.14-2.27,1.53,1.53,0,0,0-.51-.21,1.2,1.2,0,0,0-1.17.87c-1.87,5.72,7.7,10.77,12.16,11.31,9.91,1.22,17.12-7.21,19.47-15.79a43.86,43.86,0,0,0,1.13-5.79,23.09,23.09,0,0,0,0-7.42c-.18-1.22-.43-2.44-.72-3.66a21.87,21.87,0,0,0-1.13-4.29c-.48-1.38-1-2.76-1.39-4.12A28.37,28.37,0,0,0,229,76.35Zm-39.19,67.34a21.89,21.89,0,0,0,5.3.41,13.85,13.85,0,0,1-5.32-.41Z" transform="translate(0 0)" style="fill:#ccc"/>
     </svg>
    

    Step 2: Prepare your SVG

    The next step is to add ‘symbol’ tags to the elements inside of your SVG and to move the viewBox to the newly created ‘symbol’ element. You must also add an id attribute to the symbol so that you can call it externally.

    If you want to change the colors differently for each reference, you have to set the ‘fill’ and ‘outline’ attributes to ‘currentColor’ or ‘inherit’. Use ‘currentColor’ to set the color through the ‘color’ property. Use ‘inherit’ to set the color through the ‘path’ property.

    I also learned a trick from using Font Awesome icons. You can automatically make the SVG be two-colored by setting the opacity of the secondary color.

    Now, your SVG will look something like this:

    <svg xmlns="http://www.w3.org/2000/svg">
      <symbol id="ces-logo-cloud"  viewBox="0 0 228.98 148.98">
        <path opacity="0.4" d="M189.37,142a12.22,12.22,0,0,1-3.2-3c-1.26-1.65-2.31-3.5-3.71-5a12.12,12.12,0,0,0,2.48,4.77A12.6,12.6,0,0,0,189.37,142Z"/><path opacity="0.4" d="M192.89,142.94a12.74,12.74,0,0,1-3.52-1A6.2,6.2,0,0,0,192.89,142.94Z"/><path opacity="0.4" d="M182.46,133.91a11.83,11.83,0,0,1-.4-3.65,5,5,0,0,0-1.2,2.21,2.4,2.4,0,0,1,.26.18A10.68,10.68,0,0,1,182.46,133.91Z"/><path fill="currentColor" d="M229,76.35a28.34,28.34,0,0,0-19-26.61,14.29,14.29,0,0,0,1.32-6A14.8,14.8,0,0,0,196.3,29.07a15.06,15.06,0,0,0-10.47,4.2,28.12,28.12,0,0,0,.45-5C186.28,12.67,173.36,0,157.43,0a28.92,28.92,0,0,0-25.81,15.64,24.52,24.52,0,0,0-2.7-.2A29.18,29.18,0,0,0,113.33,11C97.4,11,84.48,23.62,84.48,39.25a28.26,28.26,0,0,0,.15,2.92c-2.79-.57-5.76-1.72-8.51-2.21-4.42-.54-8.85,2.71-12.19,5.47a37.46,37.46,0,0,0-3.47,3.66A31.68,31.68,0,0,0,52.9,67.61c-.56,13,10,21.14,18.8,29.27,1.1,1.63,0,4.88,1.1,5.42,3.32,3.26,6.64-2.71,6.08-6-1.1-4.88-6.08-6-8.84-9.22-3-3.25-2.91-7.58-1-11.31a14.86,14.86,0,0,1,2.49-3.44c3.45-4.22,9-3.68,13.41-2.59,7.19,2.17,14.93,6,16.6,13.59-.06,1.62-.15,3.29-.28,5-1.06,13.49-4.32,26.82-15.67,35.58-8.37,6.46-19.16,9.14-29.75,10.3s-21.32.93-31.87,2.4c14.49-4.51,29.91-5.73,44.28-10.58,7.48-2.53,14.82-6.18,19.89-12.12a35.52,35.52,0,0,0,7.68-17.8c1.05-6.79.27-14.5-4.81-19.23-3.5-3.26-9.84-4.22-12.87-.53s-1.07,9,1.5,12.82A19.62,19.62,0,0,1,78,83.06a6.66,6.66,0,0,1,2.37-5.66c2.14-1.58,5.3-1.22,7.48.29a13.1,13.1,0,0,1,4.46,6.37,28.18,28.18,0,0,1-3,25.38C82.12,120,68.58,124.6,55.72,126.23,46.5,127.4,37.17,127.4,28,128.57S-12.06,144.48,5.1,148c16,3.23,88.05-1.41,106.45-9.34s34.35-20.07,54.15-24.38c5-1.09,9.4,0,13.82,2.71,7.19,4.34,6.08,11.92,6.08,18.43,0,2.17,2.21,4.88,4.43,4.88,7.74,0,13.82-7.59,14.37-14.64-.56,7.19-5.48,20.7-15.52,16.76-2.32-.91-4.12-2.73-5.85-4.5a6.88,6.88,0,0,0,2.39,3.76,13.55,13.55,0,0,1-2.71-2.53,8.92,8.92,0,0,1-2-4.36,6,6,0,0,1,.14-2.27,1.42,1.42,0,0,0-.51-.21,1.19,1.19,0,0,0-1.17.87c-1.87,5.72,7.7,10.77,12.16,11.31,9.91,1.22,17.12-7.21,19.47-15.79a44.22,44.22,0,0,0,1.13-5.79,23.09,23.09,0,0,0,0-7.42c-.18-1.22-.43-2.44-.72-3.66a21.43,21.43,0,0,0-1.13-4.29c-.48-1.38-1-2.76-1.39-4.12A28.37,28.37,0,0,0,229,76.35Zm-39.19,67.34a21.82,21.82,0,0,0,5.3.41A13.73,13.73,0,0,1,189.79,143.69Z"/>
      </symbol>
    </svg>
    

    Step 3: Create a component that will fetch your SVGs

    svg.component.ts

    import { Component, Input, OnInit } from '@angular/core';
    import { SvgSize } from './svg';
    
    @Component({
      selector: 'ces-svg',
      templateUrl: './svg.component.html',
      styleUrls: ['./svg.component.scss']
    })
    export class SvgComponent implements OnInit {
    
    link: string = '';
    
    @Input() size: SvgSize | undefined;
    @Input() type: string = '';
    @Input() dropShadow: boolean = true;
    
    constructor() {
    }
    
    ngOnInit(): void {
      this.link = `/assets/images/svgs/${this.type}.svg#${this.type}`;
    }
    

    It’s crucial to remember you can’t just link a file to the SVG. You must add a hash tag and the name of your id to the end of the file name after the file extension. You can save on http requests if you house multiple SVGs in one file, aka sprites. But you must use unique ids for each SVG in the file.

    Also, if you want to add classes to apply styles that fit exactly to the shape of the SVG like drop shadows or borders, you have to add them directly to the SVG element or else they will be applied to the component host. However, you can define those classes in the component or as a global class.

    svg.component.html

    <svg [ngClass]="{ 'drop-shadow' : dropShadow }" [class]="size ? size : ''" xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">
      <use [attr.xlink:href]="link" [attr.href]="link" />
    </svg>
    

    I’m specifying the two href attributes because ‘xlink:href’ is being deprecated and is just there for backwards compatibility.

    svg.component.scss

    @import 'variables';
    
    svg {
      width: inherit;
      height: inherit;
    }
    
    .xs {
      width: $icon-xs-sm;
      height: $icon-xs-sm;
    }
    
    .sm {
      width: $icon-sm-sm;
      height: $icon-sm-sm;
    }
    
    .md {
      width: $icon-md-sm;
      height: $icon-md-sm;
    }
    
    .lg {
      width: $icon-lg-sm;
      height: $icon-lg-sm;
    }
    
    @media (min-width: $breakpoint-md) {
      .xs {
        width: $icon-xs-md;
        height: $icon-xs-md;
      } 
    
      .sm {
        width: $icon-sm-md;
        height: $icon-sm-md;
      }
    
      .md {
        width: $icon-md-md;
        height: $icon-md-md;
      }
    
      .lg {
        width: $icon-lg-md;
        height: $icon-lg-md;
      }
    }
    

    Don’t forget to set the width and height to ‘inherit’ using the ‘SVG’ selector or else you won’t be able to set them outside of the component.

    In order to get around the problem of not being able to view or set the viewBox per reference, I use art boards with square dimensions when exporting SVGs. That way, I don’t have to worry about getting the exact width/height ratio correct and can easily avoid distorted graphics.

    In order to enforce standardized sizes, I also created different classes for each size. I defined them using variables so different apps that use this component can have their own standardized sizes.

    Example Usage

    Alas, here is an example of how to use the SVG component we just created in a button:

    <button class="inherit" [ngClass]="{ 'active' :  active }" (click)="toggleEdit()">
      <ces-svg size="xs" name="pencil"></ces-svg>
    </button>
    

    The SVG’s styles that we set earlier will automatically inherit those of the button. But they can also be set by adding a class to the SVG component.

    Source Files

    Here is a link to the source files. Please send me any thoughts or ideas for improvement. Thanks!

  • Componentize SVGs in Angular and style them on the fly

    Componentize SVGs in Angular and style them on the fly

    As you probably know, SVGs, or scalable vector graphics, are essential if you’re using simple artwork like icons. But did you know that you can make them into Angular components? In fact, the two libraries I use the most are the ones I created for SVGs and buttons.

    Why should you use SVGs?

    I started using SVGs because of their ability to scale without any loss in resolution and because I could color them through CSS. (This was useful for a part of the gamified interactive lesson I started where you get to pick a character and make it grow or shrink.)

    Note: Only SVGs made with paths and outlines can scale indefinitely. SVGs made with images will get fuzzy.

    SVGs are also super lightweight because they are represented in XML, and you can avoid http requests to fetch them!

    Liberate yourself from SVG libraries

    What I didn’t realize was that my beloved Font Awesome library was costing me almost 20 Lighthouse points and that I didn’t need their large Javascript files to make them into tidy components (instead of unmanageable chunks of text) by turning them into Angular components.

    Note: I still subscribe to Font Awesome because of their unparalleled selection. In fact, all of the icons on the sites/apps I make are thanks to their brilliant illustrators.

    If you end up using their SVGs or make your own using Illustrator, be sure to delete any styles/comments that are part of the SVG, so that you can properly override them.

    Angular components don’t have to use HTML templates

    If you want your own Angular SVG library, you can easily turn them into components by linking the templateUrl parameter of your component to the SVG in your asset library. (Font Awesome offers a library to access their SVGs, but you can avoid unnecessary dependencies and weight to your app by going this route. As an added bonus, this approach lets you declare your SVGs once, instead of having to declare them individually in every app that uses them.)

    svg-astronaut.component.ts

    @Component({
      selector: 'ces-svg-astronaut',
      templateUrl: '../../../../../../src/assets/images/svgs/astronaut.svg',
      styleUrls: ['../../svg.component.scss']
    })
    

    Note: Initially, I was storing the assets directly in my Angular library but then moved the SVGs to my apps’ shared assets folder so that I don’t have to modify my angular.json file to use the assets and so that I could access the SVGs outside of the library.

    The downside to this decision is that the library won’t work outside of the workspace unless you include the assets folder. Please let me know if you have a better solution.

    Be careful on how you link to your SVGs

    You can’t link the SVGs using the usual “src/assets” or “/assets” shortcuts, as they require a direct relative path to your app’s source folder. That’s why I had to use a ridiculously long relative path. (Again, I’m sure there’s a better way to achieve this.)

    Use the same CSS file for all of your SVGs

    I also linked the SVGs to the same CSS file since they all require the same styles, which are required to be able to style them on the fly through the parent that’s using them. By doing so, I just need to update one file to affect all of the components in the future.

    svg.component.css

    svg {
      width: inherit;
      height: inherit;
      fill: inherit;
      background: inherit;
      background-color: inherit;
    }
    
    img {
      width: inherit;
      height: inherit;
    }
    

    Note: Don’t forget to include the components in your module’s exports section so that your app can access them. And import the SVG library into the apps that use them.

    Clean up unused files

    You can delete the html and CSS files that came with your component, as your component won’t be using them.

    Use your new component

    Now, if I want to use an SVG, I just use the component’s selector:

    <ces-svg-astronaut></ces-svg-astronaut>
    

    Because I follow the same naming convention, e.g., prefix-svg-description, I usually don’t have to look up what I called it.

    What if you want to dynamically select the SVG?

    With some use cases, you won’t know ahead of time which SVG you want to select. Because you can’t make part of the selector dynamic, this feature requires setting up a separate general component.

    svg.component.ts

    import { Component, Input } from '@angular/core';
    
    @Component({
      selector: 'ces-svg',
      template: `
          <img src="/assets/images/svgs/{{ type }}.svg" [alt]="type + ' icon'" [title]="title">`,
      styleUrls: [
      './svg.component.scss'
       ]
    })
    export class SvgComponent  {
      @Input() type: string;
      @Input() title:string;
    }
    

    Note: This is why I included styles for “img” in the above CSS. To change the color of the SVG image, you need to use these styles on an element:

    mask: url('/assets/svgs/astronaut');
    background-color: orangered;
    

    Automatically add “alt” tags and dynamically add “title” tags

    I included the “alt” attribute to automatically take the icon’s type with the word “icon” so that I didn’t have to specify them individually. However, I made the “title” attribute a variable in case I wanted to add help text that shows upon hover, which will vary depending on the usage.

    Use the general SVG component

    Now, you can use the same selector and specify which icon you want to use by passing in an icon type. You only have to set up this component once, even if you add/delete SVGs to your library.

    <ces-svg type="astronaut">