Every Front-End application includes a lot of media content. Today, I would like to talk about a small part of media content management - how to deal with SVG icons in your application. Why should you use SVG in your application? Well, there are some advantages in comparison with raster one:
color
CSS property and voilà - you have it;Let's look into the ways of using SVG icons in an application. Generally, we can handle it in different ways:
Use background
CSS property. One of the easiest ways to use any type of content in an application;
Create a script that will collect all of your icons and put them into an index.html file. Then, create a component that uses icons via use
tag and href
attribute like this:
<svg>
<use [attr.xlink:href]="'#app-icon-' + iconName" />
</svg>
This approach isn't obvious and can lead to performance issues since you can add some icons with large sizes, and they will be included (even if they aren't used) in your index.html
after an application build. This approach has been used for a long time in my current project. It became an issue when we added some icons with a size of about 350 KB, and it increased our index.html by more than three times (from 130KB to 430KB)
import { HttpClient } from '@angular/common/http';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { map, shareReplay } from 'rxjs/operators';
import { SvgIconService } from './svg-icon.service';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'app-svg-icon',
styleUrls: ['./svg-icon.component.less'],
// *1
template: ` <div [innerHTML]="sanitizedSvgContent"></div>`,
})
export class SvgIconComponent implements OnInit {
@Input() public iconName!: string;
public sanitizedSvgContent: SafeHtml;
constructor(
private cdr: ChangeDetectorRef,
private sanitizer: DomSanitizer,
private http: HttpClient,
private svgIconService: SvgIconService,
) {}
// *2
public ngOnInit(): void {
this.loadSvg();
}
// *3
private loadSvg(): void {
// Exit from the method in case of icon absence
if (!this.iconName) return;
// Construct your path to an icon
const svgPath = `/icons/svg/${this.iconName}.svg`;
// Check if the icon is already cached
if (!this.svgIconService.svgIconMap.has(svgPath)) {
// *4
const svg$ = this.http.get(svgPath, { responseType: 'text' }).pipe(
map((svg) => this.sanitizer.bypassSecurityTrustHtml(svg)),
shareReplay(1),
);
// Cache the result: iconName as a key and Observable as a value
this.svgIconService.svgIconMap.set(svgPath, svg$);
}
// Get an Observable with sanitized SVG from the Map
const cachedSvg$ = this.svgIconService.svgIconMap.get(svgPath);
// Subscribe to the Observable to get the content
cachedSvg$.subscribe(
(svg) => {
// Set it to the property
this.sanitizedSvgContent = svg;
// Trigger the 'detectChanges' method for UI updating
this.cdr.detectChanges();
},
// Simple error handling in case of any issue related to icon loading
(error) => console.error(`Error loading SVG`, error),
);
}
}
Some important comments for this code snippet:
div
element with the innerHTML
property, which allows us to insert any HTML as a string. In this place, we insert our SVG icons;ngOnInit
method we invoke the loadSvg
method, which handles the whole logic of getting a particular icon;SvgIconService
with only one field - svgIconMap
which is Map
;@Injectable({
providedIn: 'root',
})
export class SvgIconService {
public svgIconMap: Map<string, Observable<SafeHtml>> = new Map<string, Observable<SafeHtml>>();
}
HttpClient
for loading an icon by path. For operators, we use map
function, which returns sanitized content (we simply skip checking this content since we trust the source. If you load icons from sources that you do not trust 100% - it's better to use the sanitize
function), and shareReplay
to ensure that the HTTP request is made only once, and future subscribers to this Observable
will immediately retrieve an icon.:host {
display: inline-block;
height: 18px;
width: 18px;
color: inherit;
}
div {
&::ng-deep svg {
display: block;
height: 100%;
width: 100%;
color: inherit;
fill: currentColor;
}
}
width
and the height
to a host
element for the easiest way of size overriding - you can set any size you want outside by simply adding the width
and height
properties;color
and fill
for the same purpose - to get an opportunity for style override outside the component. Adding the display
, height
, and width
properties ensures that the icon will take all available space.With this solution, the index.html has been reduced from 430KB to 6KB, which
improved Page Load Time for 100-150ms in average.
Before using this component make sure that your icons are in the dist folder. You can make it by adding them to the assets property of the angular.json.
Just add the new component by selector and pass an icon name as an input property:
<app-svg-icon icon="yourIconName"></app-svg-icon>
Make sure that you put the correct name and icon in the required folder. For size and color changes, you can add a class or an ID. Specify your rules and enjoy the result!
In this article, you have learned about a simple way of creating a shared Angular component for handling SVG icons. Remember that every application has its own specific and required application-fit solution. Hope you have found this article helpful and interesting.