@h-k-dev/angular-inline-svg
v1.5.1
Published
A lightweight, signal-based Angular directive for inlining SVGs into the DOM, with caching, fallbacks, attribute manipulation, and accessibility defaults.
Maintainers
Readme
@h-k-dev/angular-inline-svg
A tiny, signal-based Angular directive that fetches an SVG by URL and inlines it directly into the DOM, so you can style and script it with CSS and JavaScript like any other element. Think of this as the modern Angular counterpart to react-inlinesvg.
💡 Legacy Browser / Older Angular Support: This package is heavily optimized for modern, cutting-edge environments. If you need to support legacy browsers or older versions of Angular, please use ng-inline-svg-2.
My objectives is to stay in the browser. While this package will NEVER adopt SSR support out of the box, I will expose functions for you to build your own pipeline on server side and keep the package as lean as possible at build time. As time goes more injection at configuration level will permit you to implement your own Hydration Strategy that hopefully allows a seemless adoption for SSR implementation.
Features
- Inline, not
<img>— the SVG lands in the DOM socurrentColor, CSS, and JS all work. - Reactive — built on Angular signals and
resource(); re-renders automatically when inputs change. - Caching — bounded LRU + TTL cache with in-flight request de-duplication.
- Fallbacks — supply a
fallbackSVGURL, with a built-in generic placeholder as a last resort. - Attribute manipulation — add or strip attributes on the inserted
<svg>(e.g.width,fill). - Accessible by default — sets
focusable="false"andaria-hidden="true"unless you provide a semantic hint. - SSR-safe — bails out cleanly on the server and only touches the DOM in the browser.
- Hardened — strips
<script>tags and inlineon*handlers as defense-in-depth.
Installation
npm install @h-k-dev/angular-inline-svgQuick start
The directive is standalone — just import it where you need it.
import { Component } from '@angular/core';
import { AngularInlineSvg } from '@h-k-dev/angular-inline-svg';
@Component({
selector: 'app-logo',
imports: [AngularInlineSvg],
template: `<span [inlineSVG]="'assets/icons/logo.svg'"></span>`,
})
export class LogoComponent {}The fetched SVG is appended to the host element, so the markup above renders as:
<span><svg ...>...</svg></span>Configuration
Configure the directive once at the application root with provideInlineSvg():
import { ApplicationConfig } from '@angular/core';
import { provideInlineSvg } from '@h-k-dev/angular-inline-svg';
export const appConfig: ApplicationConfig = {
providers: [
provideInlineSvg({
baseUrl: 'assets/icons',
cacheMaxEntries: 100,
cacheTtlMs: 5 * 60 * 1000, // 5 minutes
}),
],
};| Option | Type | Default | Description |
| ----------------- | ------------------ | ------------- | -------------------------------------------------------------------------------------------- |
| baseUrl | string | '' | Prepended to relative URLs when resolveSVGUrl is enabled. |
| cacheMaxEntries | number | 100 | Maximum cached SVGs before least-recently-used eviction. |
| cacheTtlMs | number | 300000 | How long (ms) a cached SVG stays fresh before it is refetched. |
| cacheParsedElements | boolean | true | Also keep one parsed + scrubbed master element per cached URL so repeat icons skip parse/scrub and just clone it. Disable to keep cache entries text-only. |
| fetcher | InlineSvgFetcher | native fetch| DI-free custom fetcher. For DI-based fetchers (e.g. HttpClient), use provideInlineSvgFetcher. |
Custom fetchers are responsible for their own content-type validation. The built-in default rejects responses that aren't
image/svg+xmlortext/plain.
Custom fetcher
By default the directive loads SVGs with the native fetch API. You can swap in
your own transport - to add auth headers, go through an interceptor stack, or use
a different HTTP client. A fetcher is any promise-based function matching:
type InlineSvgFetcher = (url: string, abortSignal: AbortSignal) => Promise<string>;Honor the abortSignal so in-flight requests are cancelled when the directive is
destroyed or its URL changes.
Native fetch with custom headers
For a DI-free fetcher, pass it straight to provideInlineSvg:
import { provideInlineSvg } from '@h-k-dev/angular-inline-svg';
provideInlineSvg({
fetcher: async (url, signal) => {
const res = await fetch(url, {
signal,
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
return res.text();
},
});Axios
Axios supports AbortSignal natively via its signal option:
import axios from 'axios';
import { provideInlineSvg } from '@h-k-dev/angular-inline-svg';
provideInlineSvg({
fetcher: (url, signal) =>
axios.get<string>(url, { signal, responseType: 'text' }).then((res) => res.data),
});Angular HttpClient
HttpClient needs dependency injection, so build the fetcher inside an injection
context with provideInlineSvgFetcher. Bridge the abortSignal to unsubscription
so aborts actually cancel the request:
import { inject } from '@angular/core';
import { HttpClient, provideHttpClient } from '@angular/common/http';
import { firstValueFrom, fromEvent, takeUntil } from 'rxjs';
import { provideInlineSvgFetcher } from '@h-k-dev/angular-inline-svg';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(),
provideInlineSvgFetcher(() => {
const http = inject(HttpClient);
return (url, signal) =>
firstValueFrom(
http
.get(url, { responseType: 'text' })
.pipe(takeUntil(fromEvent(signal, 'abort'))),
);
}),
],
};API
Inputs
| Input | Type | Default | Description |
| --------------------- | ----------------------------------------- | ------- | -------------------------------------------------------------------------------------------- |
| inlineSVG | string (required) | — | URL of the SVG to load and inline. |
| fallbackSVG | string | — | URL loaded instead if the main inlineSVG request fails. |
| resolveSVGUrl | boolean | true | When true, relative URLs are resolved against the configured baseUrl. |
| setSVGAttributes | Record<string, string \| number \| boolean> | — | Attributes applied to the root <svg> after load (e.g. { width: 24, fill: 'red' }). |
| removeSVGAttributes | readonly string[] | — | Attribute names stripped from the <svg> and all descendants after load. |
| useCache | boolean | true | When true, the SVG is cached and reused for subsequent requests. |
| preParse | (rawSvg: string) => string | — | Optional hook to transform/sanitize the raw SVG string before parsing (e.g. via DOMPurify). |
Outputs
| Output | Payload | Description |
| -------- | ------------ | ----------------------------------------------------------------- |
| loaded | SVGElement | Emits the inserted <svg> element once it is in the DOM. |
| failed | Error | Emits when loading or parsing fails (after any fallback fails). |
Usage examples
Setting and removing attributes
<span
[inlineSVG]="'icon.svg'"
[setSVGAttributes]="{ width: 32, height: 32, fill: 'currentColor' }"
[removeSVGAttributes]="['width', 'height']"
></span>Fallback and event handling
<span
[inlineSVG]="'maybe-missing.svg'"
[fallbackSVG]="'placeholder.svg'"
(loaded)="onLoaded($event)"
(failed)="onFailed($event)"
></span>onLoaded(svg: SVGElement) {
console.log('SVG inserted', svg);
}
onFailed(err: Error) {
console.error('SVG failed to load', err);
}Sanitizing untrusted SVGs
For SVGs from untrusted sources, run them through a sanitizer such as
DOMPurify using the preParse hook:
import DOMPurify from 'dompurify';
protected readonly sanitize = (raw: string): string => DOMPurify.sanitize(raw);<span [inlineSVG]="untrustedUrl" [preParse]="sanitize"></span>Security
The directive parses markup in a detached element (keeping it out of Angular's
sanitizer so the <svg> survives) and, as defense-in-depth, removes <script>
tags and inline on* event handlers. This is not a full sanitizer. For
genuinely untrusted input, always route the SVG through DOMPurify via preParse.
Accessibility
After loading, the directive applies sensible defaults to the root <svg>:
focusable="false"— keeps the SVG out of the tab order.aria-hidden="true"— treats the icon as decorative.
If you provide any of role, aria-label, aria-labelledby, or aria-hidden
via setSVGAttributes, the directive leaves it visible to screen readers:
<span
[inlineSVG]="'search.svg'"
[setSVGAttributes]="{ role: 'img', 'aria-label': 'Search' }"
></span>License
MIT
