From dee5ec9b2059a3ab01a9a17a41b6908528bc5378 Mon Sep 17 00:00:00 2001 From: Nazir T Date: Tue, 27 Oct 2026 12:27:27 +0300 Subject: [PATCH 1/3] perf(icon): skip the registry lookup for names nothing can resolve --- .../components/icon/icon-registry.spec.ts | 36 +++++++++++++++++++ packages/components/icon/icon-registry.ts | 16 +++++++++ .../components/icon/icon.component.spec.ts | 12 +++++++ packages/components/icon/icon.component.ts | 3 +- tools/public_api_guard/components/icon.api.md | 1 + 5 files changed, 67 insertions(+), 1 deletion(-) diff --git a/packages/components/icon/icon-registry.spec.ts b/packages/components/icon/icon-registry.spec.ts index c3fb3f725b..229b3da35e 100644 --- a/packages/components/icon/icon-registry.spec.ts +++ b/packages/components/icon/icon-registry.spec.ts @@ -5,6 +5,7 @@ import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { firstValueFrom } from 'rxjs'; import type { MockInstance } from 'vitest'; import { KbqIconRegistry } from './icon-registry'; +import { kbqIconsResolverProvider } from './icon-registry-providers'; const ICON_SVG = ''; const SPRITE_SVG = ` @@ -169,6 +170,33 @@ describe('KbqIconRegistry', () => { }); }); + describe('canResolve', () => { + it('is false on an empty registry', () => { + expect(registry.canResolve('check')).toBe(false); + }); + + it('is true only for the names registered', () => { + registry.addSvgIconLiteral('check', sanitizer.bypassSecurityTrustHtml(ICON_SVG)); + registry.addSvgIconLiteralInNamespace('brand', 'logo', sanitizer.bypassSecurityTrustHtml(ICON_SVG)); + + expect(registry.canResolve('check')).toBe(true); + expect(registry.canResolve('brand:logo')).toBe(true); + expect(registry.canResolve('close')).toBe(false); + expect(registry.canResolve('logo')).toBe(false); + }); + + it('is true for any name of a namespace with an icon set', () => { + registry.addSvgIconSetInNamespace('sprite', sanitizer.bypassSecurityTrustResourceUrl('/sprite.svg')); + + expect(registry.canResolve('sprite:anything')).toBe(true); + expect(registry.canResolve('anything')).toBe(false); + }); + + it('is true for inline SVG markup', () => { + expect(registry.canResolve(ICON_SVG)).toBe(true); + }); + }); + describe('getNamedSvgIcon errors', () => { it('errors when no icon registered', async () => { await expect(firstValueFrom(registry.getNamedSvgIcon('missing'))).rejects.toThrow('missing'); @@ -264,3 +292,11 @@ describe('KbqIconRegistry', () => { }); }); }); + +describe('KbqIconRegistry with a resolver', () => { + it('can resolve any name', () => { + TestBed.configureTestingModule({ providers: [kbqIconsResolverProvider(() => null)] }); + + expect(TestBed.inject(KbqIconRegistry).canResolve('anything')).toBe(true); + }); +}); diff --git a/packages/components/icon/icon-registry.ts b/packages/components/icon/icon-registry.ts index d0c3b2ceef..3d1f302164 100644 --- a/packages/components/icon/icon-registry.ts +++ b/packages/components/icon/icon-registry.ts @@ -103,6 +103,22 @@ export class KbqIconRegistry { this.iconSetConfigs.set(namespace, sets); } + /** + * Whether {@link getNamedSvgIcon} may find something for the name: inline markup, an icon registered under it, a + * resolver, or an icon set in its namespace. `false` means the lookup would only error. + * + * @docs-private + */ + canResolve(name: string): boolean { + if (name.trimStart().startsWith('<') || this.resolvers.length > 0) return true; + + const colonIndex = name.indexOf(':'); + const namespace = colonIndex === -1 ? '' : name.slice(0, colonIndex); + const iconName = colonIndex === -1 ? name : name.slice(colonIndex + 1); + + return this.iconConfigs.has(iconKey(namespace, iconName)) || !!this.iconSetConfigs.get(namespace)?.length; + } + /** * Emits a cloned SVGElement for the given name. * Accepts "namespace:name" syntax. diff --git a/packages/components/icon/icon.component.spec.ts b/packages/components/icon/icon.component.spec.ts index de5e506332..74ebc098af 100644 --- a/packages/components/icon/icon.component.spec.ts +++ b/packages/components/icon/icon.component.spec.ts @@ -295,6 +295,18 @@ describe('KbqIcon', () => { expect(hostOf(fixture).classList).toContain('kbq-plus_16'); }); + it('should not look up a font icon the registry cannot resolve', () => { + register('kbq-minus_16'); + + const lookup = vi.spyOn(registry, 'getNamedSvgIcon'); + const fixture = TestBed.createComponent(TestApp); + + fixture.detectChanges(); + + expect(lookup).not.toHaveBeenCalled(); + expect(hostOf(fixture).classList).toContain('kbq-plus_16'); + }); + it('should render an SVG that arrives after the first render', async () => { registry.addSvgIcon('kbq-plus_16', sanitizer.bypassSecurityTrustResourceUrl('/icons/plus.svg')); diff --git a/packages/components/icon/icon.component.ts b/packages/components/icon/icon.component.ts index 52ec8ce3e9..cac0a8669f 100644 --- a/packages/components/icon/icon.component.ts +++ b/packages/components/icon/icon.component.ts @@ -241,7 +241,8 @@ export class KbqIcon extends KbqColorDirective implements AfterContentInit, OnCh this.svgIconSubscription.unsubscribe(); - if (!this.registry || !name) { + // Checked first: every font icon goes through here, and a lookup that finds nothing builds an error per icon. + if (!this.registry || !name || !this.registry.canResolve(name)) { this.renderSvgIcon(null); return; diff --git a/tools/public_api_guard/components/icon.api.md b/tools/public_api_guard/components/icon.api.md index 1407ecefa1..d5a1f22cf2 100644 --- a/tools/public_api_guard/components/icon.api.md +++ b/tools/public_api_guard/components/icon.api.md @@ -146,6 +146,7 @@ export class KbqIconRegistry { addSvgIconLiteralInNamespace(namespace: string, name: string, literal: SafeHtml, options?: KbqIconOptions): void; addSvgIconSet(url: SafeResourceUrl, options?: KbqIconOptions): void; addSvgIconSetInNamespace(namespace: string, url: SafeResourceUrl, options?: KbqIconOptions): void; + canResolve(name: string): boolean; getNamedSvgIcon(name: string, namespace?: string): Observable; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration; From 34aed0188ef2cf7ad393b2cd2e136ccd644adb0b Mon Sep 17 00:00:00 2001 From: Nazir T Date: Tue, 27 Oct 2026 15:21:06 +0300 Subject: [PATCH 2/3] refactor(icon): share the namespace:name parsing of canResolve and getNamedSvgIcon --- packages/components/icon/icon-registry.ts | 28 +++++++++-------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/packages/components/icon/icon-registry.ts b/packages/components/icon/icon-registry.ts index 3d1f302164..0d8a3080f5 100644 --- a/packages/components/icon/icon-registry.ts +++ b/packages/components/icon/icon-registry.ts @@ -23,6 +23,13 @@ function iconKey(namespace: string, name: string): string { return namespace ? `${namespace}:${name}` : name; } +/** Splits "namespace:name" at the first colon; a name without one stays in the given namespace. */ +function parseIconName(name: string, namespace = ''): [namespace: string, name: string] { + const colonIndex = name.indexOf(':'); + + return colonIndex === -1 ? [namespace, name] : [name.slice(0, colonIndex), name.slice(colonIndex + 1)]; +} + function cloneSvg(svg: SVGElement): SVGElement { return svg.cloneNode(true) as SVGElement; } @@ -112,9 +119,7 @@ export class KbqIconRegistry { canResolve(name: string): boolean { if (name.trimStart().startsWith('<') || this.resolvers.length > 0) return true; - const colonIndex = name.indexOf(':'); - const namespace = colonIndex === -1 ? '' : name.slice(0, colonIndex); - const iconName = colonIndex === -1 ? name : name.slice(colonIndex + 1); + const [namespace, iconName] = parseIconName(name); return this.iconConfigs.has(iconKey(namespace, iconName)) || !!this.iconSetConfigs.get(namespace)?.length; } @@ -124,24 +129,13 @@ export class KbqIconRegistry { * Accepts "namespace:name" syntax. */ getNamedSvgIcon(name: string, namespace = ''): Observable { - let resolvedNs = namespace; - let resolvedName = name; - - if (resolvedName.trimStart().startsWith('<')) { + if (name.trimStart().startsWith('<')) { // `defer` so that unparsable markup arrives as an error notification: every failure of this // class has to reach the caller through the same channel. - return defer(() => - of(this.svgElementFromText(this.sanitizer.sanitize(SecurityContext.HTML, resolvedName) ?? '')) - ); - } - - const colonIndex = name.indexOf(':'); - - if (colonIndex !== -1) { - resolvedNs = name.slice(0, colonIndex); - resolvedName = name.slice(colonIndex + 1); + return defer(() => of(this.svgElementFromText(this.sanitizer.sanitize(SecurityContext.HTML, name) ?? ''))); } + const [resolvedNs, resolvedName] = parseIconName(name, namespace); const key = iconKey(resolvedNs, resolvedName); const iconConfig = this.iconConfigs.get(key); From e1bf47c108f70b53fcfa4fc93b3c8ff21d9019ed Mon Sep 17 00:00:00 2001 From: lskramarov Date: Wed, 28 Oct 2026 12:15:11 +0300 Subject: [PATCH 3/3] refactor(icon): look up through one method that reports a miss as null (#DS-5798) Replace the public `canResolve` predicate with an internal `findSvgIcon` that `getNamedSvgIcon` wraps, so the check and the lookup cannot drift apart. It also takes the namespace argument and asks the resolvers, so a name a dictionary resolver does not hold builds no error either. `KbqIcon` asks `getNamedSvgIcon` itself when a registry replaces it (a subclass, a test double), keeps the lookup inside `defer`, and sizes the icon from the name `parseIconName` resolves. --- .../components/icon/icon-registry.spec.ts | 47 ++++++----- packages/components/icon/icon-registry.ts | 42 ++++++---- .../components/icon/icon.component.spec.ts | 82 ++++++++++++++++++- packages/components/icon/icon.component.ts | 17 ++-- tools/public_api_guard/components/icon.api.md | 1 - 5 files changed, 142 insertions(+), 47 deletions(-) diff --git a/packages/components/icon/icon-registry.spec.ts b/packages/components/icon/icon-registry.spec.ts index 229b3da35e..48c6a82244 100644 --- a/packages/components/icon/icon-registry.spec.ts +++ b/packages/components/icon/icon-registry.spec.ts @@ -5,7 +5,7 @@ import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { firstValueFrom } from 'rxjs'; import type { MockInstance } from 'vitest'; import { KbqIconRegistry } from './icon-registry'; -import { kbqIconsResolverProvider } from './icon-registry-providers'; +import { kbqIconsDictProvider, kbqIconsProvider } from './icon-registry-providers'; const ICON_SVG = ''; const SPRITE_SVG = ` @@ -170,30 +170,28 @@ describe('KbqIconRegistry', () => { }); }); - describe('canResolve', () => { - it('is false on an empty registry', () => { - expect(registry.canResolve('check')).toBe(false); + describe('findSvgIcon', () => { + it('is null on an empty registry', () => { + expect(registry.findSvgIcon('check')).toBeNull(); }); - it('is true only for the names registered', () => { + it('finds only the names registered', () => { registry.addSvgIconLiteral('check', sanitizer.bypassSecurityTrustHtml(ICON_SVG)); registry.addSvgIconLiteralInNamespace('brand', 'logo', sanitizer.bypassSecurityTrustHtml(ICON_SVG)); - expect(registry.canResolve('check')).toBe(true); - expect(registry.canResolve('brand:logo')).toBe(true); - expect(registry.canResolve('close')).toBe(false); - expect(registry.canResolve('logo')).toBe(false); + expect(registry.findSvgIcon('check')).not.toBeNull(); + expect(registry.findSvgIcon('brand:logo')).not.toBeNull(); + expect(registry.findSvgIcon('logo', 'brand')).not.toBeNull(); + expect(registry.findSvgIcon('close')).toBeNull(); + expect(registry.findSvgIcon('logo')).toBeNull(); }); - it('is true for any name of a namespace with an icon set', () => { + it('leaves any name of a namespace with an icon set to the set', () => { registry.addSvgIconSetInNamespace('sprite', sanitizer.bypassSecurityTrustResourceUrl('/sprite.svg')); - expect(registry.canResolve('sprite:anything')).toBe(true); - expect(registry.canResolve('anything')).toBe(false); - }); - - it('is true for inline SVG markup', () => { - expect(registry.canResolve(ICON_SVG)).toBe(true); + expect(registry.findSvgIcon('sprite:anything')).not.toBeNull(); + expect(registry.findSvgIcon('anything', 'sprite')).not.toBeNull(); + expect(registry.findSvgIcon('anything')).toBeNull(); }); }); @@ -293,10 +291,19 @@ describe('KbqIconRegistry', () => { }); }); -describe('KbqIconRegistry with a resolver', () => { - it('can resolve any name', () => { - TestBed.configureTestingModule({ providers: [kbqIconsResolverProvider(() => null)] }); +describe('KbqIconRegistry configured through providers', () => { + it('finds only the names a resolver answers for', () => { + TestBed.configureTestingModule({ providers: [kbqIconsDictProvider({ check: '/check.svg' })] }); + + const registry = TestBed.inject(KbqIconRegistry); + + expect(registry.findSvgIcon('check')).not.toBeNull(); + expect(registry.findSvgIcon('close')).toBeNull(); + }); + + it('leaves any bare name to a sprite configured without a namespace', () => { + TestBed.configureTestingModule({ providers: [kbqIconsProvider({ spriteUrl: '/sprite.svg' })] }); - expect(TestBed.inject(KbqIconRegistry).canResolve('anything')).toBe(true); + expect(TestBed.inject(KbqIconRegistry).findSvgIcon('anything')).not.toBeNull(); }); }); diff --git a/packages/components/icon/icon-registry.ts b/packages/components/icon/icon-registry.ts index 0d8a3080f5..39704de573 100644 --- a/packages/components/icon/icon-registry.ts +++ b/packages/components/icon/icon-registry.ts @@ -23,13 +23,21 @@ function iconKey(namespace: string, name: string): string { return namespace ? `${namespace}:${name}` : name; } -/** Splits "namespace:name" at the first colon; a name without one stays in the given namespace. */ -function parseIconName(name: string, namespace = ''): [namespace: string, name: string] { +/** + * Splits "namespace:name" at the first colon; a name without one stays in the given namespace. + * + * @internal + */ +export function parseIconName(name: string, namespace = ''): [namespace: string, name: string] { const colonIndex = name.indexOf(':'); return colonIndex === -1 ? [namespace, name] : [name.slice(0, colonIndex), name.slice(colonIndex + 1)]; } +function isSvgMarkup(text: string): boolean { + return text.trimStart().startsWith('<'); +} + function cloneSvg(svg: SVGElement): SVGElement { return svg.cloneNode(true) as SVGElement; } @@ -111,25 +119,27 @@ export class KbqIconRegistry { } /** - * Whether {@link getNamedSvgIcon} may find something for the name: inline markup, an icon registered under it, a - * resolver, or an icon set in its namespace. `false` means the lookup would only error. - * - * @docs-private + * Emits a cloned SVGElement for the given name. + * Accepts "namespace:name" syntax. */ - canResolve(name: string): boolean { - if (name.trimStart().startsWith('<') || this.resolvers.length > 0) return true; + getNamedSvgIcon(name: string, namespace = ''): Observable { + const icon = this.findSvgIcon(name, namespace); + + if (icon) return icon; - const [namespace, iconName] = parseIconName(name); + const key = iconKey(...parseIconName(name, namespace)); - return this.iconConfigs.has(iconKey(namespace, iconName)) || !!this.iconSetConfigs.get(namespace)?.length; + return throwError(() => Error(`KbqIconRegistry: no icon registered for "${key}".`)); } /** - * Emits a cloned SVGElement for the given name. - * Accepts "namespace:name" syntax. + * `getNamedSvgIcon` without the error for a miss: `null` when nothing registered can resolve the name. Every font + * icon is looked up, and an error per miss would cost a stack trace each. + * + * @internal */ - getNamedSvgIcon(name: string, namespace = ''): Observable { - if (name.trimStart().startsWith('<')) { + findSvgIcon(name: string, namespace = ''): Observable | null { + if (isSvgMarkup(name)) { // `defer` so that unparsable markup arrives as an error notification: every failure of this // class has to reach the caller through the same channel. return defer(() => of(this.svgElementFromText(this.sanitizer.sanitize(SecurityContext.HTML, name) ?? ''))); @@ -156,7 +166,7 @@ export class KbqIconRegistry { options: undefined }; - if (result.trimStart().startsWith('<')) { + if (isSvgMarkup(result)) { const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, result) ?? ''; config.svgText = this.sanitizer.bypassSecurityTrustHtml(sanitized); @@ -175,7 +185,7 @@ export class KbqIconRegistry { return this.loadIconFromSets(resolvedName, sets); } - return throwError(() => Error(`KbqIconRegistry: no icon registered for "${key}".`)); + return null; } private cacheIcon(namespace: string, name: string, config: KbqSvgIconConfig): this { diff --git a/packages/components/icon/icon.component.spec.ts b/packages/components/icon/icon.component.spec.ts index 74ebc098af..983ef4090a 100644 --- a/packages/components/icon/icon.component.spec.ts +++ b/packages/components/icon/icon.component.spec.ts @@ -1,9 +1,10 @@ import { provideHttpClient } from '@angular/common/http'; import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; -import { ChangeDetectionStrategy, Component, Signal, signal, SimpleChange } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Injectable, Signal, signal, SimpleChange } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By, DomSanitizer } from '@angular/platform-browser'; import { KbqComponentColors } from '@koobiq/components/core'; +import { Observable, of } from 'rxjs'; import { KbqIconButton } from './icon-button.component'; import { KBQ_ICON_ERROR_STATE_CONTEXT } from './icon-error-state-context'; import { KbqIconRegistry } from './icon-registry'; @@ -14,6 +15,10 @@ const svgOf = (path: string) => ` + +`; + @Component({ imports: [KbqIcon], template: ` @@ -295,10 +300,15 @@ describe('KbqIcon', () => { expect(hostOf(fixture).classList).toContain('kbq-plus_16'); }); - it('should not look up a font icon the registry cannot resolve', () => { + // A miss through `getNamedSvgIcon` builds an error, with its stack trace, for every font icon on the page. + it('should not build an error for a font icon the registry cannot resolve', () => { register('kbq-minus_16'); - const lookup = vi.spyOn(registry, 'getNamedSvgIcon'); + // On the prototype: an own property would read as a replaced lookup and be asked instead. + const lookup = vi.spyOn(KbqIconRegistry.prototype, 'getNamedSvgIcon'); + + onTestFinished(() => lookup.mockRestore()); + const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); @@ -307,6 +317,31 @@ describe('KbqIcon', () => { expect(hostOf(fixture).classList).toContain('kbq-plus_16'); }); + it('should still look up a name of a namespace with an icon set', async () => { + registry.addSvgIconSetInNamespace('sprite', sanitizer.bypassSecurityTrustResourceUrl('/sprite.svg')); + + const fixture = TestBed.createComponent(TestApp); + + fixture.componentInstance.name.set('sprite:check_16'); + fixture.detectChanges(); + + TestBed.inject(HttpTestingController).expectOne('/sprite.svg').flush(SPRITE_SVG); + await fixture.whenStable(); + + expect(hostOf(fixture).querySelector('svg')?.getAttribute('width')).toBe('16'); + }); + + it('should size an svg whose name has a colon after the namespace', () => { + registry.addSvgIconLiteralInNamespace('brand', 'logo:dark_24', sanitizer.bypassSecurityTrustHtml(ICON_SVG)); + + const fixture = TestBed.createComponent(TestApp); + + fixture.componentInstance.name.set('brand:logo:dark_24'); + fixture.detectChanges(); + + expect(hostOf(fixture).querySelector('svg')?.getAttribute('width')).toBe('24'); + }); + it('should render an SVG that arrives after the first render', async () => { registry.addSvgIcon('kbq-plus_16', sanitizer.bypassSecurityTrustResourceUrl('/icons/plus.svg')); @@ -511,6 +546,47 @@ describe('KbqIcon', () => { expect(hostOf(fixture).querySelector('svg')).not.toBeNull(); }); + // The registry is public: an application may replace its lookup, and its specs may provide a double of it. + describe('replaced registry lookup', () => { + const svgElement = (): SVGElement => { + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + + svg.setAttribute('viewBox', '0 0 16 16'); + + return svg; + }; + + it('should ask a registry subclass that overrides getNamedSvgIcon', () => { + @Injectable() + class AppIconRegistry extends KbqIconRegistry { + override getNamedSvgIcon(name: string): Observable { + return name === 'kbq-plus_16' ? of(svgElement()) : super.getNamedSvgIcon(name); + } + } + + TestBed.configureTestingModule({ providers: [{ provide: KbqIconRegistry, useClass: AppIconRegistry }] }); + + const fixture = TestBed.createComponent(TestApp); + + fixture.detectChanges(); + + expect(hostOf(fixture).querySelector('svg')).not.toBeNull(); + expect(hostOf(fixture).classList).not.toContain('kbq-plus_16'); + }); + + it('should ask a test double that only has getNamedSvgIcon', () => { + TestBed.configureTestingModule({ + providers: [{ provide: KbqIconRegistry, useValue: { getNamedSvgIcon: () => of(svgElement()) } }] + }); + + const fixture = TestBed.createComponent(TestApp); + + fixture.detectChanges(); + + expect(hostOf(fixture).querySelector('svg')).not.toBeNull(); + }); + }); + // The color class of an error icon is `kbq-error` too; the `kbq-error` class binding must not take it away. describe('error color', () => { const iconOf = (fixture: ComponentFixture): KbqIcon => diff --git a/packages/components/icon/icon.component.ts b/packages/components/icon/icon.component.ts index cac0a8669f..924103beb8 100644 --- a/packages/components/icon/icon.component.ts +++ b/packages/components/icon/icon.component.ts @@ -15,9 +15,9 @@ import { ViewEncapsulation } from '@angular/core'; import { KbqColorDirective, KbqComponentColors } from '@koobiq/components/core'; -import { defer, Subscription } from 'rxjs'; +import { defer, of, Subscription } from 'rxjs'; import { KBQ_ICON_ERROR_STATE_CONTEXT } from './icon-error-state-context'; -import { KbqIconRegistry } from './icon-registry'; +import { KbqIconRegistry, parseIconName } from './icon-registry'; /** Marks an injected `` with the name it renders, so hydration can recognize and adopt it. */ const ICON_NAME_ATTRIBUTE = 'data-kbq-icon-name'; @@ -241,18 +241,22 @@ export class KbqIcon extends KbqColorDirective implements AfterContentInit, OnCh this.svgIconSubscription.unsubscribe(); - // Checked first: every font icon goes through here, and a lookup that finds nothing builds an error per icon. - if (!this.registry || !name || !this.registry.canResolve(name)) { + if (!this.registry || !name) { this.renderSvgIcon(null); return; } const registry = this.registry; + // A registry that replaces the lookup, such as a subclass overriding `getNamedSvgIcon` or a test double, is + // asked through it: only the library's own lookup can report a miss without building an error. + const ownLookup = registry.getNamedSvgIcon === KbqIconRegistry.prototype.getNamedSvgIcon; // Deferred so that a resolver or a literal that throws falls back to the font class as well, and the // next name is resolved again. - this.svgIconSubscription = defer(() => registry.getNamedSvgIcon(name)).subscribe({ + this.svgIconSubscription = defer( + () => (ownLookup ? registry.findSvgIcon(name) : registry.getNamedSvgIcon(name)) ?? of(null) + ).subscribe({ next: (svg) => this.renderSvgIcon(svg), error: () => this.renderSvgIcon(null) }); @@ -310,8 +314,7 @@ export class KbqIcon extends KbqColorDirective implements AfterContentInit, OnCh } private parseIconSize(): number | undefined { - const iconName = this.iconName; - const baseName = iconName?.includes(':') ? iconName.split(':')[1] : iconName; + const baseName = this.iconName && parseIconName(this.iconName)[1]; const size = parseInt(baseName?.split('_').pop() ?? ''); return Number.isNaN(size) ? undefined : size; diff --git a/tools/public_api_guard/components/icon.api.md b/tools/public_api_guard/components/icon.api.md index d5a1f22cf2..1407ecefa1 100644 --- a/tools/public_api_guard/components/icon.api.md +++ b/tools/public_api_guard/components/icon.api.md @@ -146,7 +146,6 @@ export class KbqIconRegistry { addSvgIconLiteralInNamespace(namespace: string, name: string, literal: SafeHtml, options?: KbqIconOptions): void; addSvgIconSet(url: SafeResourceUrl, options?: KbqIconOptions): void; addSvgIconSetInNamespace(namespace: string, url: SafeResourceUrl, options?: KbqIconOptions): void; - canResolve(name: string): boolean; getNamedSvgIcon(name: string, namespace?: string): Observable; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration;