Skip to content

Commit 14cbbb1

Browse files
committed
feat(svg): expose SvgBaseDirective to allow custom icon component implementations
1 parent b235d40 commit 14cbbb1

3 files changed

Lines changed: 72 additions & 50 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { HttpErrorResponse } from '@angular/common/http'
2+
import { Directive, effect, ElementRef, inject, Renderer2, Signal } from '@angular/core'
3+
import { Logger } from '@shiftcode/logger'
4+
5+
import { SvgRegistry } from './svg-registry.service'
6+
7+
/**
8+
* Base class for components that display an SVG element inline.
9+
* The SVG content is directly inlined as a child of the component, so that CSS styles can easily be applied to it.
10+
*/
11+
@Directive()
12+
export abstract class SvgBaseDirective {
13+
protected readonly elRef = inject<ElementRef<HTMLElement>>(ElementRef)
14+
protected readonly renderer = inject(Renderer2)
15+
protected readonly svgRegistry = inject(SvgRegistry)
16+
17+
protected abstract readonly logger: Logger
18+
protected abstract data: Signal<{ url: string; attrs?: Record<string, string> }>
19+
20+
constructor() {
21+
effect(() => {
22+
const { url, attrs } = this.data()
23+
this.getAndSet(url, attrs)
24+
})
25+
}
26+
27+
private getAndSet(url: string, attrs?: Record<string, string>) {
28+
this.svgRegistry
29+
.getFromUrl(url)
30+
.then(this.getSvgModifyFn(attrs))
31+
.then(this.setSvgElement)
32+
.catch((err: any) => {
33+
if (err instanceof HttpErrorResponse && err.status === 0) {
34+
// in case of no internet or a timeout log a warning, we can not do anything about that
35+
this.logger.warn(`Error retrieving icon for path ${url}, due to no network`, err)
36+
} else {
37+
this.logger.error(`Error retrieving icon for path ${url}`, err)
38+
}
39+
})
40+
}
41+
42+
private readonly getSvgModifyFn = (attrs?: Record<string, string>) => {
43+
const attrsEntries = attrs ? Object.entries(attrs) : []
44+
if (attrsEntries.length === 0) {
45+
return (svg: SVGElement): SVGElement => svg
46+
}
47+
48+
return (svg: SVGElement): SVGElement => {
49+
for (const [key, val] of attrsEntries) {
50+
svg.setAttribute(key, val)
51+
}
52+
return svg
53+
}
54+
}
55+
56+
private readonly setSvgElement = (svg: SVGElement | null) => {
57+
// Remove existing child nodes and add the new SVG element.
58+
this.elRef.nativeElement.innerHTML = ''
59+
this.renderer.appendChild(this.elRef.nativeElement, svg)
60+
}
61+
}
Lines changed: 10 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
import { HttpErrorResponse } from '@angular/common/http'
2-
import { ChangeDetectionStrategy, Component, effect, ElementRef, inject, input, Renderer2 } from '@angular/core'
1+
import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core'
32
import { Logger } from '@shiftcode/logger'
43
import { LoggerService } from '@shiftcode/ngx-core'
54

6-
import { SvgRegistry } from './svg-registry.service'
5+
import { SvgBaseDirective } from './svg-base.directive'
76

87
/**
98
* Standalone SvgComponent to display svg inline.
10-
* (Initially copied from material MdIcon Directive but got rid of unused functionality and refactored to Promises)
119
*
1210
* - Specify the url input to load an SVG icon from a URL.
1311
* The SVG content is directly inlined as a child of the <sc-svg> component,
1412
* so that CSS styles can easily be applied to it.
15-
* The URL is loaded via an XMLHttpRequest, so it must be on the same domain as the page or its
13+
* The URL is loaded via Angular's {@link HttpClient}, it must be on the same domain as the page or its
1614
* server must be configured to allow cross-domain requests.
1715
* @example
18-
* <sc-svg url="assets/arrow.svg"></sc-svg>
16+
* <sc-svg url="assets/arrow.svg" />
1917
*/
2018
@Component({
2119
selector: 'sc-svg',
@@ -24,52 +22,14 @@ import { SvgRegistry } from './svg-registry.service'
2422
styleUrls: ['./svg.component.scss'],
2523
changeDetection: ChangeDetectionStrategy.OnPush,
2624
})
27-
export class SvgComponent {
25+
export class SvgComponent extends SvgBaseDirective {
2826
readonly url = input.required<string>()
2927

3028
readonly attrs = input<Record<string, string>>({})
3129

32-
protected readonly elRef: ElementRef<HTMLElement> = inject(ElementRef)
33-
protected readonly renderer = inject(Renderer2)
34-
protected readonly svgRegistry = inject(SvgRegistry)
35-
36-
private readonly logger: Logger = inject(LoggerService).getInstance('SvgComponent')
37-
38-
constructor() {
39-
effect(() => {
40-
this.loadAndSetSvg(this.url(), this.attrs())
41-
})
42-
}
43-
44-
private loadAndSetSvg(url: string, attrs: Record<string, string>) {
45-
if (!url.endsWith('.svg')) {
46-
this.logger.warn('svg url does not end with *.svg')
47-
}
48-
this.svgRegistry
49-
.getFromUrl(url)
50-
.then(this.modifySvgElement(attrs))
51-
.then(this.setSvgElement)
52-
.catch((err: any) => {
53-
if (err instanceof HttpErrorResponse && err.status === 0) {
54-
// in case of no internet or a timeout log a warning, we can not do anything about that
55-
this.logger.warn(`Error retrieving icon for path ${this.url()}, due to no network`, err)
56-
} else {
57-
this.logger.error(`Error retrieving icon for path ${this.url()}`, err)
58-
}
59-
})
60-
}
61-
62-
private modifySvgElement(attrs: Record<string, string>) {
63-
return (svg: SVGElement): SVGElement => {
64-
Object.keys(attrs).forEach((key) => svg.setAttribute(key, attrs[key]))
65-
return svg
66-
}
67-
}
68-
69-
private setSvgElement = (svg: SVGElement | null) => {
70-
const layoutElement = this.elRef.nativeElement
71-
// Remove existing child nodes and add the new SVG element.
72-
layoutElement.innerHTML = ''
73-
this.renderer.appendChild(layoutElement, svg)
74-
}
30+
protected readonly logger: Logger = inject(LoggerService).getInstance('SvgComponent')
31+
protected readonly data = computed(() => ({
32+
url: this.url(),
33+
attrs: this.attrs(),
34+
}))
7535
}

libs/components/src/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// svg
22
export * from './lib/svg/svg.component'
3+
export * from './lib/svg/svg-base.directive'
34
export * from './lib/svg/svg-registry.service'
45

56
// svg-animate

0 commit comments

Comments
 (0)