Angular Icon Registry

Saturday, October 05, 2024 angularAngular Icon Registry

In this guide, you will learn how to build a fully functional icon component in Angular from scratch. We will demonstrate how to convert SVG files into TypeScript code and dynamically inject these icons into your HTML. This method avoids image loading delays and ensures full typing, enhancing both the performance and consistency of your application.

Step 1: Install the Required Library

We'll begin by installing the svg-to-ts package, which will allow us to convert SVG files into a single TypeScript file.

npm install --save-dev svg-to-ts

Step 2: Configure the SVG-to-TypeScript Conversion

Next, create a configuration file svg-to-ts.json in the root directory with the following content:

svg-to-ts.json
{
  "conversionType": "object",
  "srcFiles": [
    "./icons/*.svg"
  ],
  "svgoConfig": {
    "plugins": [
      "cleanupAttrs"
    ]
  },
  "outputDirectory": "./src/app/components/icon",
  "fileName": "icon.registry",
  "delimiter": "KEBAB",
  "typeName": "Icon"
}

Ensure that the configuration is compatible with the installed version of the svg-to-ts package. The example above is based on version [email protected].

Step 3: Add Icons to Your Project

Place all your SVG icons in the icons directory. If you don't have an icon ready, you can use the following example to practice:

icons/card.svg
<svg height="16" stroke-linejoin="round" viewBox="0 0 16 16" width="16">
  <path fill-rule="evenodd" clip-rule="evenodd"
        d="M14 3H2C1.72386 3 1.5 3.22386 1.5 3.5V5L14.5 5V3.5C14.5 3.22386 14.2761 3 14 3ZM1.5 12.5V6.5L14.5 6.5V12.5C14.5 12.7761 14.2761 13 14 13H2C1.72386 13 1.5 12.7761 1.5 12.5ZM2 1.5C0.895431 1.5 0 2.39543 0 3.5V12.5C0 13.6046 0.895431 14.5 2 14.5H14C15.1046 14.5 16 13.6046 16 12.5V3.5C16 2.39543 15.1046 1.5 14 1.5H2ZM4 10.75C4.41421 10.75 4.75 10.4142 4.75 10C4.75 9.58579 4.41421 9.25 4 9.25C3.58579 9.25 3.25 9.58579 3.25 10C3.25 10.4142 3.58579 10.75 4 10.75Z"
        fill="currentColor"/>
</svg>

It's essential that each icon has a fixed size and uses the attribute fill="currentColor". This allows the icon to inherit color from its parent element, enabling flexible color styling with CSS.

Step 4: Convert SVG Icons to TypeScript

Run the following command to convert the SVG files to TypeScript:

svg-to-ts-object --config ./svg-to-ts.json

After the process completes, you'll find the output in src/app/components/icon/icon.registry.ts. The generated TypeScript file maps icon names to their SVG content, offering full type checking and reducing the risk of naming errors.

src/app/components/icon/icon.registry.ts
/* 🤖 this file was generated by svg-to-ts */
export default {
  card: '<svg height="16" stroke-linejoin="round" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" clip-rule="evenodd" d="M14 3H2C1.72386 3 1.5 3.22386 1.5 3.5V5L14.5 5V3.5C14.5 3.22386 14.2761 3 14 3ZM1.5 12.5V6.5L14.5 6.5V12.5C14.5 12.7761 14.2761 13 14 13H2C1.72386 13 1.5 12.7761 1.5 12.5ZM2 1.5C0.895431 1.5 0 2.39543 0 3.5V12.5C0 13.6046 0.895431 14.5 2 14.5H14C15.1046 14.5 16 13.6046 16 12.5V3.5C16 2.39543 15.1046 1.5 14 1.5H2ZM4 10.75C4.41421 10.75 4.75 10.4142 4.75 10C4.75 9.58579 4.41421 9.25 4 9.25C3.58579 9.25 3.25 9.58579 3.25 10C3.25 10.4142 3.58579 10.75 4 10.75Z" fill="currentColor"/></svg>',
}

Whenever new SVG files are added to the icons folder, rerun the conversion command to update the TypeScript registry.

Step 5: Create the Icon Component

Now, we will create an IconComponent that will reference the generated icon.registry.ts file and dynamically inject the correct icon into the DOM using Angular’s DomSanitizer.

src/app/components/icon/icon.component.html
<!-- leave this file empty -->
src/app/components/icon/icon.component.scss
:host {
  display: block;
  box-sizing: border-box;
  user-select: none;
  color: #222;
  width: 16px;
  height: 16px;

  &::ng-deep svg {
    display: block;
    box-sizing: border-box;
    height: inherit;
    width: inherit;
    color: inherit;
    transition: color .3s cubic-bezier(0.645, 0.045, 0.355, 1);

    path {
      color: inherit;
    }
  }
}
src/app/components/icon/icon.component.ts
import {DomSanitizer, SafeHtml} from '@angular/platform-browser';
import {Component, computed, inject, input} from '@angular/core';
import {default as icons, type Icon} from "./icon.registry";

@Component({
  selector: 'app-icon',
  templateUrl: './icon.component.html',
  styleUrl: './icon.component.scss',
  host: {
    '[innerHTML]': 'svg()',
  },
})
export class IconComponent {
  public readonly name = input.required<Icon>();
  public readonly svg = computed<SafeHtml>(() => {
    return this.domSanitizer.bypassSecurityTrustHtml(icons[this.name()]);
  });

  private readonly domSanitizer = inject(DomSanitizer)
}

Remember to include the IconComponent in your application's module declarations.

Step 6: Use the Icon Component

You can now use the icon component in your templates as follows:

<app-icon name="card" />

Step 7: Customizing Icon Styles

To customize the size or color of the icons, you can override the styles of the app-icon component as needed:

app-icon {
  color: red;
  width: 40px;
  height: 40px;
}

By following this approach, you gain the benefits of TypeScript's IntelliSense, reducing the likelihood of errors, while also enabling flexible style overrides and instant icon loading across your Angular application.