Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/styleguide/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shiftcode/styleguide",
"version": "15.0.0",
"version": "15.1.0-pr80.0",
"private": true,
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"useNx": false,
"packages": ["libs/*", "apps/*"],
"version": "15.0.0",
"version": "15.1.0-pr80.0",
"command": {
"version": {
"allowBranch": "*",
Expand Down
2 changes: 1 addition & 1 deletion libs/components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shiftcode/ngx-components",
"version": "15.0.0",
"version": "15.1.0-pr80.0",
"repository": "https://github.com/shiftcode/sc-ng-commons-public",
"license": "MIT",
"author": "shiftcode GmbH <team@shiftcode.ch>",
Expand Down
61 changes: 61 additions & 0 deletions libs/components/src/lib/svg/svg-base.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { HttpErrorResponse } from '@angular/common/http'
import { Directive, effect, ElementRef, inject, Renderer2, Signal } from '@angular/core'
import { Logger } from '@shiftcode/logger'

import { SvgRegistry } from './svg-registry.service'

/**
* Base class for components that display an SVG element inline.
* The SVG content is directly inlined as a child of the component, so that CSS styles can easily be applied to it.
*/
@Directive()
export abstract class SvgBaseDirective {
protected readonly elRef = inject<ElementRef<HTMLElement>>(ElementRef)
protected readonly renderer = inject(Renderer2)
protected readonly svgRegistry = inject(SvgRegistry)

protected abstract readonly logger: Logger
protected abstract data: Signal<{ url: string; attrs?: Record<string, string> }>

constructor() {
effect(() => {
const { url, attrs } = this.data()
this.getAndSet(url, attrs)
})
}

private getAndSet(url: string, attrs?: Record<string, string>) {
this.svgRegistry
.getFromUrl(url)
.then(this.getSvgModifyFn(attrs))
.then(this.setSvgElement)
.catch((err: any) => {
if (err instanceof HttpErrorResponse && err.status === 0) {
// in case of no internet or a timeout log a warning, we can not do anything about that
this.logger.warn(`Error retrieving icon for path ${url}, due to no network`, err)
} else {
this.logger.error(`Error retrieving icon for path ${url}`, err)
}
})
Comment thread
mumenthalers marked this conversation as resolved.
Outdated
}

private readonly getSvgModifyFn = (attrs?: Record<string, string>) => {
const attrsEntries = attrs ? Object.entries(attrs) : []
if (attrsEntries.length === 0) {
return (svg: SVGElement): SVGElement => svg
}

return (svg: SVGElement): SVGElement => {
for (const [key, val] of attrsEntries) {
svg.setAttribute(key, val)
}
return svg
}
}

private readonly setSvgElement = (svg: SVGElement | null) => {
// Remove existing child nodes and add the new SVG element.
this.elRef.nativeElement.innerHTML = ''
this.renderer.appendChild(this.elRef.nativeElement, svg)
}
Comment thread
mumenthalers marked this conversation as resolved.
Outdated
}
60 changes: 10 additions & 50 deletions libs/components/src/lib/svg/svg.component.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import { HttpErrorResponse } from '@angular/common/http'
import { ChangeDetectionStrategy, Component, effect, ElementRef, inject, input, Renderer2 } from '@angular/core'
import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core'
import { Logger } from '@shiftcode/logger'
import { LoggerService } from '@shiftcode/ngx-core'

import { SvgRegistry } from './svg-registry.service'
import { SvgBaseDirective } from './svg-base.directive'

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

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

protected readonly elRef: ElementRef<HTMLElement> = inject(ElementRef)
protected readonly renderer = inject(Renderer2)
protected readonly svgRegistry = inject(SvgRegistry)

private readonly logger: Logger = inject(LoggerService).getInstance('SvgComponent')

constructor() {
effect(() => {
this.loadAndSetSvg(this.url(), this.attrs())
})
}

private loadAndSetSvg(url: string, attrs: Record<string, string>) {
if (!url.endsWith('.svg')) {
this.logger.warn('svg url does not end with *.svg')
}
this.svgRegistry
.getFromUrl(url)
.then(this.modifySvgElement(attrs))
.then(this.setSvgElement)
.catch((err: any) => {
if (err instanceof HttpErrorResponse && err.status === 0) {
// in case of no internet or a timeout log a warning, we can not do anything about that
this.logger.warn(`Error retrieving icon for path ${this.url()}, due to no network`, err)
} else {
this.logger.error(`Error retrieving icon for path ${this.url()}`, err)
}
})
}

private modifySvgElement(attrs: Record<string, string>) {
return (svg: SVGElement): SVGElement => {
Object.keys(attrs).forEach((key) => svg.setAttribute(key, attrs[key]))
return svg
}
}

private setSvgElement = (svg: SVGElement | null) => {
const layoutElement = this.elRef.nativeElement
// Remove existing child nodes and add the new SVG element.
layoutElement.innerHTML = ''
this.renderer.appendChild(layoutElement, svg)
}
protected readonly logger: Logger = inject(LoggerService).getInstance('SvgComponent')
protected readonly data = computed(() => ({
url: this.url(),
attrs: this.attrs(),
}))
}
1 change: 1 addition & 0 deletions libs/components/src/public-api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// svg
export * from './lib/svg/svg.component'
export * from './lib/svg/svg-base.directive'
export * from './lib/svg/svg-registry.service'

// svg-animate
Expand Down
2 changes: 1 addition & 1 deletion libs/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shiftcode/ngx-core",
"version": "15.0.0",
"version": "15.1.0-pr80.0",
"repository": "https://github.com/shiftcode/sc-ng-commons-public",
"license": "MIT",
"author": "shiftcode GmbH <team@shiftcode.ch>",
Expand Down