TeqBench Material Icons

@teqbench/tbx-mat-icons
angularmaterialv4.1.0

Abstract icon service contracts for Angular Material — a generic TbxMatIconService base plus two strategy subclasses (TbxMatSvgIconService for inline SVG via MatIconRegistry and TbxMatFontIconService for font ligature resolution) that consumers extend to register typed icon keys.

@teqbench/tbx-mat-icons provides the abstract service contracts that every other @teqbench package uses to register and resolve icons. It is not a runtime library of icons — it is a service pattern. Consumers subclass one of the two concrete strategies (SVG or font) and override initialize() to register the icon keys they need, producing a typed, DI-friendly service that the rest of the application can inject and call resolve(key) on.

The abstract design is deliberate: icon registration is inherently per-application domain work (what icons does this app need, mapped to what keys), but the mechanics of registration, resolution, default fontSet cascading, and integration with Angular Material's MatIconRegistry are identical across every such service. By shipping the mechanics as reusable abstract bases and leaving the domain registration to subclasses, applications get a typed icon service with ~10 lines of subclass code.

Two rendering strategies

The package supports two icon rendering strategies in parallel, each backed by a dedicated abstract base:

  • SVG strategyTbxMatSvgIconService registers raw SVG markup with Angular Material's MatIconRegistry via the integrated DomSanitizer. Consumers bind <mat-icon [svgIcon]="service.resolve(key)">. Good for custom-designed brand icons, icon sprites, or any case where font-based rendering isn't an option.
  • Font strategyTbxMatFontIconService registers ligature name mappings (e.g. Severity.Success → 'check_circle') and resolves a fontSet from a three-step precedence chain (constructor argument → TBX_MAT_FONT_ICON_DEFAULT_FONT_SET token → MAT_ICON_DEFAULT_OPTIONS). Consumers bind <mat-icon [fontSet]="service.fontSet">{{ service.resolve(key) }}</mat-icon>. Good for Material Symbols and any other icon font.

Both strategies share TbxMatIconService as their common base — it owns the registration map, the register() / resolve() / reset() methods, and the iconType discriminant that lets consumers branch on rendering strategy at runtime without coupling to implementation details. A single typed service can therefore be consumed polymorphically: iconType === 'svg' → use [svgIcon]; iconType === 'font' → use text-content + optional [fontSet].

Generic typing

The abstract services are generic over T extends string. Consumers that want compile-time safety on icon keys declare their service with an enum or union type as T:

enum Severity { Success = 'success', Error = 'error' }
class SeverityIconService extends TbxMatFontIconService<Severity> { … }
// service.resolve('typo') → TypeScript error
// service.resolve(Severity.Success) → 'check_circle'

Consumers happy with plain strings can omit the generic and get T = string. The generic flows through register() so the same compile-time constraint applies when populating the registry — you cannot accidentally register an unknown key.

Default fontSet cascade

Font icons need to know which font family to render with. The three-step cascade is designed to satisfy three common setups without ambiguity:

  1. Per-service: pass a fontSet string to super() in the subclass constructor. Scoped to that one service.
  2. Application-wide tbx default: provide TBX_MAT_FONT_ICON_DEFAULT_FONT_SET in app.config.ts. All TbxMatFontIconService subclasses that don't pass a constructor argument inherit it.
  3. Angular Material-wide default: provide MAT_ICON_DEFAULT_OPTIONS with a fontSet. Angular Material's <mat-icon> already uses this as its global default, so the service picks up the same value and consumers don't need to bind [fontSet].

Step 3 is the minimal-setup path (no binding, no token); step 1 is the explicit per-service override; step 2 is the escape hatch when you want tbx services on a different fontSet than the rest of <mat-icon> usage.

When to use

Use @teqbench/tbx-mat-icons any time you need a typed, DI-friendly icon service in an Angular Material application — either for your own application's icons, or as the foundation for a distributable icon package (like @teqbench/tbx-mat-severity-icons).

Do not use it for:

  • Ad-hoc <mat-icon> usage — if you just need a handful of icons referenced by ligature name in a template, use <mat-icon> directly. This package is for cases where an enum-keyed service makes sense.
  • Non-Angular projects — this package depends on Angular Material and MatIconRegistry.
  • Runtime icon downloads — the services register icons upfront via subclass initialize(). For dynamic icon sources, build directly against MatIconRegistry.

At a glance

  • Abstract-base service pattern — Three generic abstract classes (base, SVG, font) that consumers extend to register typed icon keys with ~10 lines of subclass code.
  • SVG rendering strategy — TbxMatSvgIconService registers raw SVG markup with Angular Material's MatIconRegistry for rendering via the mat-icon svgIcon binding.
  • Font rendering strategy — TbxMatFontIconService maps keys to ligature names and resolves a fontSet for rendering via mat-icon text content with an optional fontSet binding.
  • Strategy discriminant — Both services expose an iconType property (Font or Svg) so consumers can branch on rendering strategy at runtime without coupling to implementation details.
  • Compile-time-safe icon keys — Services are generic over T extends string — use an enum or union for typed keys with compile-time protection against typos in register() and resolve().
  • Three-step fontSet cascade — Constructor argument → TBX_MAT_FONT_ICON_DEFAULT_FONT_SET token → MAT_ICON_DEFAULT_OPTIONS — pick whichever matches your setup without ambiguity.
  • Bundled Material Symbols constants — Three fontSet constants (rounded, outlined, sharp) for the common Material Symbols variants that Google ships, so consumers avoid magic strings.
  • Override-friendly registration — register() replaces any previous mapping, so subclasses can override parent defaults; call initialize() again to restore the default set.

Concepts

  • Icon key — The domain-specific name a consumer registers and resolves icons by — typically a value from an enum or string-union type.
  • Rendering strategy — How the resolved icon value is drawn — either SVG (inline markup via MatIconRegistry) or font (ligature name in a specific fontSet).
  • iconType discriminant — The readonly Font or Svg value on every icon service that tells consumers which Angular Material binding to use at the call site.
  • Ligature — A named glyph inside an icon font (e.g. check_circle) that renders when the font is applied and the name is placed as text content.
  • fontSet — The CSS font-family identifier for an icon font (e.g. material-symbols-rounded) — the font the ligature is drawn from.
  • fontSet cascade — The three-step precedence for resolving a font service's fontSet — constructor argument, then TBX_MAT_FONT_ICON_DEFAULT_FONT_SET token, then MAT_ICON_DEFAULT_OPTIONS.
  • MatIconRegistry — Angular Material's shared registry that maps svgIcon names to inline SVG markup and is accessed by mat-icon when rendering via svgIcon binding.
  • Subclass initialize() — The protected hook consumers override in concrete icon services to populate the registry via register() calls; invoked once from the base-class constructor.

Services

TbxMatFontIconService

Abstract base class for font-based icon services

Extends TbxMatIconService with fontSet resolution for Angular Material font icon rendering. Subclasses register domain keys mapped to font ligature names via register()resolve() is inherited from the base class and returns the ligature for a given key.

How registration works:

register(name, value) is inherited from the base class. For font icons, name is the domain key (e.g. an enum member like 'success') and value is the font ligature (e.g. 'check_circle'). The two are typically different, unlike SVG icons where they are identical.

After registration, resolve(name) returns the ligature, which the component renders as text content inside <mat-icon>.

fontSet resolution:

The fontSet value is determined by the first match in this order:

  1. Explicit constructor argument — super('material-symbols-sharp'). The service uses a specific fontSet regardless of any global configuration. The consuming component must bind [fontSet]="icons.fontSet" on <mat-icon> so the icon renders with the correct font family.

  2. TBX_MAT_FONT_ICON_DEFAULT_FONT_SET token — set once in app.config.ts. All subclasses that call super() without an argument inherit this value. The consuming component must bind [fontSet]="icons.fontSet" on <mat-icon>.

  3. MAT_ICON_DEFAULT_OPTIONS.fontSetAngular Material's global icon default. When this is the source, <mat-icon> already uses the correct fontSet globally, so the component does not need a [fontSet] binding.

  4. Error — if none of the above provides a fontSet, the constructor throws.

When to bind [fontSet] in the component:

  • Steps 1 and 2: The service uses a fontSet that differs from (or is independent of) the global MAT_ICON_DEFAULT_OPTIONS. The component must bind [fontSet]="icons.fontSet" to override the global for that icon.

  • Step 3: The fontSet comes from MAT_ICON_DEFAULT_OPTIONS, which <mat-icon> already uses by default. No [fontSet] binding is needed.

This is the font counterpart to TbxMatSvgIconService. SVG services register inline SVG markup with MatIconRegistry; font services map domain keys to ligature names.

When to use

Extend this class to create a service that maps domain-specific icon keys to Material Symbols font ligature names. Override initialize() to call register(key, ligature) for each icon. Optionally pass a fontSet to super() or provide TBX_MAT_FONT_ICON_DEFAULT_FONT_SET.

Example

Explicit fontSet via constructor (overrides all globals):

enum Severity {
    Success = 'success',
    Error = 'error',
}

// SharpIconService is a hypothetical consumer-defined subclass
@Injectable({ providedIn: 'root' })
export class SharpIconService extends TbxMatFontIconService<Severity> {
    constructor() {
        super(TBX_MAT_ICON_FONT_SET_MATERIAL_SYMBOLS_SHARP);
    }

    protected override initialize(): void {
        super.initialize();
        this.register(Severity.Success, 'check_circle');
        this.register(Severity.Error, 'cancel');
    }
}

// Component injects the service and binds [fontSet]:
// readonly icons = inject(SharpIconService);
// readonly severity = Severity.Success;
// <mat-icon [fontSet]="icons.fontSet">{{ icons.resolve(severity) }}</mat-icon>
// resolve(Severity.Success) → 'check_circle'

Example

fontSet from TBX_MAT_FONT_ICON_DEFAULT_FONT_SET token:

// app.config.ts
providers: [
    { provide: TBX_MAT_FONT_ICON_DEFAULT_FONT_SET, useValue: TBX_MAT_ICON_FONT_SET_MATERIAL_SYMBOLS_ROUNDED },
]

// MySeverityIconService is a hypothetical consumer-defined subclass
@Injectable({ providedIn: 'root' })
export class MySeverityIconService extends TbxMatFontIconService<Severity> {
    protected override initialize(): void {
        super.initialize();
        this.register(Severity.Success, 'check_circle');
        this.register(Severity.Error, 'cancel');
    }
}

// Component injects the service and binds [fontSet]:
// readonly icons = inject(MySeverityIconService);
// <mat-icon [fontSet]="icons.fontSet">{{ icons.resolve(severity) }}</mat-icon>

Example

fontSet from MAT_ICON_DEFAULT_OPTIONS (global default):

// app.config.ts
providers: [
    { provide: MAT_ICON_DEFAULT_OPTIONS, useValue: { fontSet: 'material-symbols-rounded' } },
]

// MySeverityIconService is a hypothetical consumer-defined subclass
@Injectable({ providedIn: 'root' })
export class MySeverityIconService extends TbxMatFontIconService<Severity> {
    protected override initialize(): void {
        super.initialize();
        this.register(Severity.Success, 'check_circle');
        this.register(Severity.Error, 'cancel');
    }
}

// Component injects the service — no [fontSet] binding needed:
// readonly icons = inject(MySeverityIconService);
// <mat-icon>{{ icons.resolve(severity) }}</mat-icon>

TbxMatIconService

Abstract base class for all icon services

Provides the core registration and resolution mechanics shared by both font-based (TbxMatFontIconService) and SVG-based (TbxMatSvgIconService) icon services. Subclasses inherit register() and resolve() and only add behaviour specific to their icon type (e.g. MatIconRegistry interaction for SVG, fontSet resolution for fonts).

How registration works:

Internally, a Map<string, string> stores name -> value pairs:

  • Font icons — name is the domain key (e.g. an enum member like 'success') and value is the font ligature (e.g. 'check_circle'). The two are typically different.

  • SVG icons — name is the domain key and the svgIcon binding name. The SVG markup itself is stored externally by MatIconRegistry, so the base class stores name -> name (identity mapping). The TbxMatSvgIconService override handles this automatically.

Re-registering the same name replaces the previous value, allowing subclasses to override defaults set by a parent class.

How resolution works:

resolve(name) returns the stored value for a given name, or undefined if the name was never registered. The typed overload encourages callers to use the constrained TName type while the string overload provides a fallback for dynamic lookups.

Do not extend this class directly — extend one of the concrete intermediate classes instead:

  • TbxMatFontIconService — for font ligature icons

  • TbxMatSvgIconService — for inline SVG icons

When to use

Do not extend this class directly. Extend TbxMatFontIconService for font ligature icons or TbxMatSvgIconService for inline SVG icons. Override initialize() to register domain-specific icon mappings.

Example

Font icon subclass:

enum Severity { Success = 'success', Error = 'error' }

// SeverityIconService is a hypothetical consumer-defined subclass
@Injectable({ providedIn: 'root' })
export class SeverityIconService extends TbxMatFontIconService<Severity> {
    protected override initialize(): void {
        super.initialize();
        this.register(Severity.Success, 'check_circle');
        this.register(Severity.Error, 'cancel');
    }
}
// resolve(Severity.Success) → 'check_circle'

Example

SVG icon subclass:

enum Brand { Logo = 'logo', Wordmark = 'wordmark' }

// BrandIconService is a hypothetical consumer-defined subclass
@Injectable({ providedIn: 'root' })
export class BrandIconService extends TbxMatSvgIconService<Brand> {
    protected override initialize(): void {
        super.initialize();
        this.register(Brand.Logo, '<svg>…</svg>');
        this.register(Brand.Wordmark, '<svg>…</svg>');
    }
}
// resolve(Brand.Logo) → 'logo'

TbxMatSvgIconService

Abstract base class for SVG-based icon services

Extends TbxMatIconService with Angular Material's MatIconRegistry and DomSanitizer integration. Subclasses call the protected register() method with a name and SVG markup string — they never interact with MatIconRegistry or DomSanitizer directly.

Once registered, icons are available in templates via <mat-icon [svgIcon]="name">.

How registration works:

register(name, svg) does two things:

  1. Passes name, name to the base class — the resolved value for an SVG icon is the icon name itself (used in the [svgIcon] binding), not the SVG markup.

  2. Registers the SVG markup with MatIconRegistry via addSvgIconLiteral(), sanitized through DomSanitizer.

Re-registering the same name replaces the previous entry in both the base class registry and MatIconRegistry.

This is the SVG counterpart to TbxMatFontIconService. Font services map domain keys to ligature names; SVG services register inline SVG markup with the Material icon registry.

When to use

Extend this class to create a service that registers inline SVG icons with Angular Material's MatIconRegistry. Override initialize() to call register(name, svgMarkup) for each icon. Consumers bind [svgIcon]="icons.resolve(key)" on <mat-icon>.

Example

enum BrandIcon {
    Logo = 'logo',
    Wordmark = 'wordmark',
}

const BRAND_SVG: Record<BrandIcon, string> = {
    [BrandIcon.Logo]: '<svg>…</svg>',
    [BrandIcon.Wordmark]: '<svg>…</svg>',
};

// BrandSvgIconService is a hypothetical consumer-defined subclass
@Injectable({ providedIn: 'root' })
export class BrandSvgIconService extends TbxMatSvgIconService<BrandIcon> {
    protected override initialize(): void {
        super.initialize();
        for (const [name, svg] of Object.entries(BRAND_SVG)) {
            this.register(name, svg);
        }
    }
}

// Component uses svgIcon binding:
// readonly icons = inject(BrandSvgIconService);
// <mat-icon [svgIcon]="icons.resolve(BrandIcon.Logo)!"></mat-icon>
// resolve(BrandIcon.Logo) → 'logo'

Interfaces

TbxMatIconResolver

Contract for resolving icon keys to usable icon identifiers

Implemented by both TbxMatFontIconService (font ligature resolution) and TbxMatSvgIconService (SVG icon registration and resolution).

When to use

Use this interface to type service references when the consumer does not need to know whether icons are font-based or SVG-based. Inject the concrete service and type the reference as TbxMatIconResolver<MyEnum>.

Enums

TbxMatIconType

Discriminant for the icon rendering strategy

Set by each intermediate service class and used by consumers to determine the correct <mat-icon> binding:

  • Font — render as font ligature text content: <mat-icon>ligature</mat-icon>

  • Svg — render via svgIcon binding: <mat-icon [svgIcon]="name"></mat-icon>

When to use

Read this value from a service's iconType property to conditionally bind either text content or [svgIcon] on <mat-icon> in a template that must support both icon types.

Constants

TBX_MAT_FONT_ICON_DEFAULT_FONT_SET

Injection token for setting a default fontSet at the application level

When provided (typically in the root ApplicationConfig), any TbxMatFontIconService subclass that does not pass a fontSet to super() will use this value automatically.

When to use

Provide this token once in app.config.ts to set a consistent font set across all font icon services in the application. Individual services can still override by passing a fontSet argument to super().

Example

// app.config.ts
import { TBX_MAT_FONT_ICON_DEFAULT_FONT_SET, TBX_MAT_ICON_FONT_SET_MATERIAL_SYMBOLS_ROUNDED }
    from '@teqbench/tbx-mat-icons';

export const appConfig: ApplicationConfig = {
    providers: [
        { provide: TBX_MAT_FONT_ICON_DEFAULT_FONT_SET, useValue: TBX_MAT_ICON_FONT_SET_MATERIAL_SYMBOLS_ROUNDED },
    ],
};

Accessibility

Not applicable — abstract service contracts, no UI surface. Consumers that render <mat-icon> with the values produced by these services are responsible for pairing each icon with an accessible label (either via adjacent text, aria-label, or aria-hidden="true" when the icon is decorative).