Better icon management by using svgs with TypeScript. Gone are the days of having to create and manage your icon "font", tons of PNGs, or worse, variations of both!
With standards increasing in capabilities, the traditional way of handling icons is going extinct. Try scaling, animating, and positioning that icon font, bad boy ;)
Let me introduce you to managing your icons using svgs and type safety.
- Dramatically reduce HTTP requests by using inlining.
- Never track down why an icon is magically missing because of typos.
- Leverage typescript tree-shaking to nuke unused icons.
- Distribute your
Icons.ts
as a node package on npmjs.com or similar. - Optimize even further with an extensive set of tooling options.
Setup
First, install the node package svg-to-ts:
npm install svg-to-ts
Now, update your package.json
by adding a scripts
target for build:icons
and then the svg-to-ts
object below:
{
"name": "@myorg/icons",
"version": "0.0.0",
"scripts": {
"build:icons": "svg-to-ts-object",
},
"devDependencies": {
"svg-to-ts": "^9.0.0",
"typescript": "~4.8.4"
},
"svg-to-ts": {
"srcFiles": [
"./assets/icons/**/*.svg"
],
"outputDirectory": "./src/app/shared/icons",
"interfaceName": "Icon",
"typeName": "Icon",
"prefix": "icon",
"svgoConfig": {
"plugins": [
"cleanupAttrs"
]
},
"fileName": "icons"
}
}
Running
Simply run npm run build:icons
from your project root to generate your icons.ts
file containing all of your svg's inlined:

After completion (1-2 seconds), your outputDirectory
should contain an icon.ts
like:

Opening icons.ts
will look something like this:
Implementing
Now that we have our SVGs inlined, typed, and optimized, we're ready to start using this in our view!
I created a custom component called app-icon
which is just a wrapper around, ultimately injecting the string value of my Icons
values to make things cleaner and quicker:
import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, Input, ViewChild, ViewContainerRef } from '@angular/core';
import tippy from 'tippy.js';
import Icons, { Icon } from './icons';
type IconSize = 6 | 8 | 10 | 12 | 14 | 16 | 20;
@Component({
selector: 'app-icon',
template: `
<div #container class="flex gap-x-2.5 items-center" [class.h-full]="tooltip" [ngClass]="classes">
<div #wrapper [ngStyle]="{ height: height + 'px', width: width + 'px' }"></div>
<ng-content></ng-content>
</div>
`
})
export class IconComponent implements AfterViewInit {
@ViewChild('container', { read: ViewContainerRef }) private container: ViewContainerRef;
@ViewChild('wrapper', { read: ElementRef }) private svg: ElementRef;
@Input() name: string;
@Input() classes: string;
@Input() tooltip: string;
@Input() width: IconSize | number;
@Input() height: IconSize | number;
public constructor(private readonly changeDetectorRef: ChangeDetectorRef) {
}
public ngAfterViewInit() {
this.width = this.width * 4;
if (!this.height) {
this.height = this.width;
}
this.svg.nativeElement.innerHTML = Icons[this.name as Icon];
if (this.tooltip) {
tippy(this.container.element.nativeElement, {
theme: 'light',
content: this.tooltip,
placement: 'bottom',
animation: 'shift-away-extreme',
animateFill: true,
followCursor: true,
inertia: true,
allowHTML: true
});
}
this.changeDetectorRef.detectChanges();
}
}
IconComponent
to your module's declarations: []
!Now you can start using your fancy icon library by dropping in:
<app-icon [name]="'bookmark'" [width]="8"></app-icon>
Remember, name
is an @Input()
of type Icon
and takes a string value matching one of the values defined for each icon.
You can also reference the value typed as Icon
programatically:

Because this is an svg you cannot just bind to [innerHTML]
and hope it works.
You'll need to set this yourself like so:
import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, Input, ViewChild, ViewContainerRef } from '@angular/core';
import Icons, { Icon } from './icons';
@Component({
selector: 'app-icon',
template: `
<div #wrapper style="width: 50px; height: 50px;"></div>
`
})
export class IconComponent implements AfterViewInit {
@ViewChild('wrapper', { read: ElementRef }) private svg: ElementRef;
@Input() name: Icon;
public constructor(private readonly changeDetectorRef: ChangeDetectorRef) {
}
public ngAfterViewInit() {
this.svg.nativeElement.innerHTML = Icons[this.name as Icon];
this.changeDetectorRef.detectChanges();
}
}