We have an Angular 16 Universal project and we want to find the best way to use SVG icons. Performance is crucial for our web app. We cannot use fonts such as Icomoon because the icons are multicolor, and it gets hard to customize and maintain.
First, we developed an Angular directive that inlines the icons in runtime. We tried the following modes:
Client-only: inlines the icons via a HttpClient.get() when the app runs in the browser. However, the icons do not start being downloaded until the whole main.js (which contains the directive) is loaded. This causes a perceptible flickering.
SSR + Client: with Angular hydration activated, the server performs the get calls to get the icons and the client wont repeat said calls. This solves the flickering, because the returned page already contains the SVGs. However, I am concerned about the creation of a bottleneck when introducing these requests in the server-side.
Moreover, the icons are currently served by an assets server, and we want to be able to send our components as a library to other teams so that they may reuse them. This could create problems if those teams make requests to our assets server from different hostnames (CORS). Therefore, some suggestions were made:
Inlining the SVGs on build time, specially for those that are critical and must be always shown. This would fix the potential issues of our directive, but it would increase the size of the scripts. Moreover, I haven't found a simple way to configure it via Webpack. Pasting them directly in our templates seems like an undesirable solution.
Using the assets folder so that the icons are included in the dist folder when passing our library to other teams.
Which would be the best way to include icons as SVG taking into account all these ideas?
Expanding on Eliseo's answer, you might consider a method to preload SVG icons during the application initialization, and then utilize an Angular component to display these icons inline, allowing for CSS customization.
That would provide an alternative approach to achieve inlining SVGs without needing to delve into Webpack configurations.
The structure of your Angular project would be:
Create a service to handle fetching and storing SVG icons.
And use
APP_INITIALIZERto preload SVG icons during application initialization: that should mitigate the perceptible flickering mentioned in the "Client-only" mode and possibly alleviate the server bottleneck concern in the "SSR + Client" mode.src/app/core/services/svg.service.ts:The
loadSvgIconsmethod fetches the SVG icons from the local asset file ('assets/symbol-defs.svg') and stores the SVG markup in thesvgDefsproperty. ThegetIcon(iconId: string)method is used to retrieve specific SVG icon data from thesvgDefsproperty.Then create an Angular component to render SVG icons inline.
src/shared/components/svg-icon/svg-icon.component.ts:The
SvgIconComponentis designed to render SVG icons inline. Theiconinput property is used to specify the icon ID. TheiconSvgproperty is used to store the SVG icon string. ThengOnChangeslifecycle hook is used to update theiconSvgproperty when theiconinput property changes.In your Angular module file (e.g.,
app.module.ts), you would import theSVG_ICON_INITIALIZERfrom thesvg.service.tsfile and add it to theprovidersarray to make sure the SVG icons are preloaded during application initialization.src/app/app.module.ts:For illustration, in a typical Angular project, you might have a
headerdirectory within acomponentsdirectory or directly under theappdirectory, containing aheader.component.htmlfile for the template of the header component.In the
header.component.htmlfile, you would use thesvg-iconcomponent to render SVG icons inline:src/app/components/header/header.component.html(template file for the header component):Now that SVG icons are rendered inline, you can apply CSS styles as needed.
You get:
APP_INITIALIZER(usingSVG_ICON_INITIALIZER) triggers theloadSvgIcons()method inSvgServiceduring Angular's initialization phase.SvgServicefetches the SVG icons from the external asset file (assets/symbol-defs.svg).SvgIconComponentutilizesSvgServiceto obtain specific SVG icon data via thegetIcon(iconId: string)method whenever there is a change in theiconinput property (triggeringngOnChanges()).SvgIconComponentrenders the SVG icon inline within its template using Angular's[innerHTML]binding to inject the SVG markup into the DOM.By encapsulating the SVG handling within a service and a component, that facilitates the reusability and sharing of components with other teams: you should be able to pass the library to other teams
Note: That does use a local asset file ('
assets/symbol-defs.svg') which is bundled with the application during the build process.This is straightforward and avoids any network requests at runtime to fetch the SVG icons, which mitigates potential CORS issues. However, it does not leverage an external asset server.
In scenarios where the SVG icons are expected to change frequently or when there is a need to share icons across multiple projects, using an external asset server could be beneficial.
You would need to update the
SvgServiceto fetch the SVG icons from the asset server instead of the local 'assets/symbol-defs.svg' file.src/app/core/services/svg.service.ts:But you will also need to handle CORS issues that may arise when fetching the SVG icons from an external asset server.
Make sure the asset server is configured to allow cross-origin requests from the domains where your Angular applications are hosted. That can typically be done by setting appropriate CORS headers on the asset server.
See more at "Angular CORS Guide: Fixing errors", from Saujan Ghimire
You might also need to implement versioning and cache-busting strategies to make sure the latest version of the SVG icons are fetched from the asset server whenever there are updates. That can be done by appending a version query parameter to the URL when fetching the SVG icons.
The
versionvariable can be hardcoded to 'v1'. Whenever there is an update to the SVG icons, you would update this value to a new version string, e.g., 'v2', 'v3', and so forth. That change in the URL triggers the browser to fetch the updated SVG icons from the server, bypassing any cached version.src/environments/environment.ts:By placing the
SVG_ICON_VERSIONvariable in the environment file, you allow for environment-specific versioning of your SVG icons. For example, you might have a different version of the SVG icons in your development environment compared to your production environment.Then you would update the
loadSvgIcons()method in theSvgServiceto append the version query parameter to the URL when fetching the SVG icons.src/app/core/services/svg.service.ts:That would leverage an external asset server to serve SVG icons, facilitating easier updates and sharing of icons across projects. However, it does introduce a network request at runtime to fetch the SVG icons, which could potentially introduce a bottleneck.
APP_INITIALIZER(which triggersloadSvgIconsmethod inSvgService).SvgServicesends a request to fetch SVG icons from the external asset server, appending a version query parameter to the URL.SvgServicestores the fetched SVG icons for later use.SvgServicefor displaying SVG icons inline.True, the
APP_INITIALIZERtoken is used to execute functions when an application starts, but it does run after themain.jsfile is loaded.If
main.jsbecomes large and takes a significant amount of time to load, there would still be a perceptible delay before the SVG icons are fetched and displayed, potentially leading to flickering.To mitigate this, you have serval approaches:
You can try and optimize the bundle size: reducing the size of the
main.jsbundle would result in faster loading times. That can be achieved through code-splitting, tree-shaking, and other bundle optimization techniques.With server-side rendering, the SVG icons could be fetched and inlined into the HTML on the server before it is sent to the client. That would eliminate the flickering since the icons would already be present on the initial render.
Another approach would be to implement a service worker to cache the SVG icons after the first load can eliminate network latency in subsequent loads. The service worker could preload essential assets, including SVG icons, ensuring they are available as soon as the app loads.
If the SVG icons are hosted on a Content Delivery Network (CDN), they could be loaded more quickly than from a single server, especially if the CDN is optimized for delivering static assets.
HTTP/2 or HTTP/3 protocols can also help in loading assets in parallel, reducing the overall load time.