Frameworks

Icons in Angular

glyphfox-angular adds a standalone gf-icon component and a provider that registers icons by name. It supports Angular 17 and newer, server rendering, OnPush and zoneless change detection.

Install

terminal
npm install glyphfox glyphfox-angular

glyphfox-angular is published in the Angular package format, built with ng-packagr, and uses glyphfox for the icon data and the rendering engine. Its peer dependency is @angular/core 17 or newer, because the inputs rely on input transforms such as booleanAttribute.

Register icons with provideGlyphFox

gf-icon looks icons up by name, so register the ones your templates use. Every icon is a named data export of glyphfox: home is iconHome, arrow-right is iconArrowRight.

src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { iconCheck, iconFile, iconHome, iconMenu, iconSave, iconSearch, iconX } from 'glyphfox';
import { provideGlyphFox } from 'glyphfox-angular';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideGlyphFox({
      icons: [iconHome, iconSearch, iconFile, iconMenu, iconX, iconSave, iconCheck],
      defaults: { weight: 1.75 },
    }),
  ],
};

defaults applies to every gf-icon under that injector, and an input on the element wins over the default. Several provideGlyphFox calls in the same injector add up: their icons are all registered and their defaults are merged in order. provideGlyphFoxIcons(icons) is the short form when you only register icons.

To make every icon available by name, pass the full record: import { icons } from 'glyphfox' and provideGlyphFox({ icons }). That puts the data of all 419 icons in the bundle that loads the provider, about 105 KB minified and 25 KB gzip, so keep it for prototypes and internal tools. Two named data exports cost well under 1 KB.

Use gf-icon

src/app/toolbar.component.ts
import { Component, signal } from '@angular/core';
import { GlyphFoxIcon } from 'glyphfox-angular';

@Component({
  selector: 'app-toolbar',
  standalone: true, // the default from Angular 19
  imports: [GlyphFoxIcon],
  template: `
    <a href="/" aria-label="Home"><gf-icon name="home" /></a>
    <gf-icon name="search" size="20" />
    <gf-icon name="file+check@tr" variant="duotone" secondaryColor="#ff7a1a" />
    <button type="button" aria-label="Menu" [attr.aria-expanded]="open()" (click)="open.set(!open())">
      <gf-icon name="menu" morphTo="x" trigger="state" [active]="open()" />
    </button>
  `,
})
export class Toolbar {
  readonly open = signal(false);
}

Inputs use the camelCase prop names: secondaryColor, modifierCorner, flipX. Static strings work for every input, numeric ones included (size="20" becomes the number 20), and boolean inputs accept a bare attribute, so <gf-icon name="arrow-right" mirrorRtl /> mirrors the arrow in right to left layouts.

class and style go on the gf-icon host, an inline flex box around the svg. Strokes use currentColor, so the CSS color of the host or of any parent colors the icon, and the color input sets it directly.

Modifiers, styles and fusion

name takes the full name syntax. file+check adds a badge, @tl, @tr, @bl or @br picks its corner, and heart/solid picks a style. Badges are drawn by the engine, so only the base icon has to be registered. For a fusion, pass the parts to [icons] and choose a layout.

src/app/status.component.html
<gf-icon name="cloud+sync@tr" />
<gf-icon name="heart/solid" color="#e5484d" />
<gf-icon [icons]="['cloud', 'lock']" layout="overlap" [gap]="1" />
<gf-icon [icons]="['user', 'shield']" layout="badge" />
<gf-icon [icons]="['file', 'image', 'music', 'video']" layout="grid" />

A fusion is resolved by name like any other icon, so every part must be registered. The result follows weight, variant and the other inputs, and where parts overlap the part behind is cut around the part in front. Layouts are overlap, side, stack, grid, orbit, badge and inside.

Icon data instead of names

The icon input takes icon data directly and skips the registry, which suits icons picked in code from a fixed map.

src/app/file-row.component.ts
import { Component, computed, input } from '@angular/core';
import { iconFile, iconFileCode, iconFileImage, iconFileText } from 'glyphfox';
import { GlyphFoxIcon } from 'glyphfox-angular';

const byType = { code: iconFileCode, image: iconFileImage, text: iconFileText };
type FileType = keyof typeof byType;

@Component({
  selector: 'app-file-row',
  standalone: true,
  imports: [GlyphFoxIcon],
  template: `<gf-icon [icon]="icon()" size="18" /> {{ name() }}`,
})
export class FileRow {
  readonly name = input.required<string>();
  readonly type = input<FileType | null>(null);
  readonly icon = computed(() => {
    const type = this.type();
    return type ? byType[type] : iconFile;
  });
}

Icons for lazy routes

provideGlyphFox returns environment providers, so it also works in the providers of a route. Declared in a lazily loaded routes file, feature icons travel with the lazy chunk instead of the main bundle.

src/app/icons.ts
import { iconHome, iconMenu, iconSearch, iconX } from 'glyphfox';
import type { GlyphFoxConfig } from 'glyphfox-angular';

export const sharedIcons = {
  icons: [iconHome, iconSearch, iconMenu, iconX],
  defaults: { weight: 1.75 },
} satisfies GlyphFoxConfig;
src/app/app.routes.ts
import { Routes } from '@angular/router';

export const routes: Routes = [
  { path: 'billing', loadChildren: () => import('./billing/billing.routes').then((m) => m.BILLING_ROUTES) },
];
src/app/billing/billing.routes.ts
import { Routes } from '@angular/router';
import { iconCreditCard, iconReceipt, iconWallet } from 'glyphfox';
import { provideGlyphFox } from 'glyphfox-angular';
import { sharedIcons } from '../icons';
import { BillingPage } from './billing.page';

export const BILLING_ROUTES: Routes = [
  {
    path: '',
    providers: [
      provideGlyphFox({ ...sharedIcons, icons: [...sharedIcons.icons, iconCreditCard, iconReceipt, iconWallet] }),
    ],
    component: BillingPage,
  },
];

A route that calls provideGlyphFox gets its own configuration. Angular does not merge multi providers across injectors, so components under that route read only the route call, including its defaults. Spreading a shared object, as above, keeps the root icons and defaults in place.

Server rendering and hydration

gf-icon writes its svg through Renderer2, so with Angular SSR or prerendering the icon is already in the HTML, and the browser redraws the same markup when the component initializes. The server has no requestAnimationFrame: a morph goes straight to its end value there, and a morph with the default trigger (mount) only plays once the app boots in the browser.

Register every icon the server renders. A name that is not registered is fetched with a dynamic import, and that request can still be pending when the server serializes the page, which leaves an empty icon in the HTML until hydration fills it in.

The same loader explains a detail of production builds: the output lists one small chunk per icon, because the builder bundles every possible target of that dynamic import. Those chunks are only downloaded for names you did not register.

OnPush, signals and zoneless apps

gf-icon uses OnPush and draws its svg itself whenever an input changes, on hover and click triggers, and on every frame of a morph. It does not depend on zone.js, so it works in zoneless apps, and signals bind like any other value: [active]="saved()".

src/app/save-button.component.ts
import { Component, signal } from '@angular/core';
import { GlyphFoxIcon } from 'glyphfox-angular';

@Component({
  selector: 'app-save-button',
  standalone: true,
  imports: [GlyphFoxIcon],
  template: `
    <button type="button" (click)="save()">
      <gf-icon name="save" morphTo="check" trigger="state" [active]="saved()" [duration]="300" />
      {{ saved() ? 'Saved' : 'Save' }}
    </button>
  `,
})
export class SaveButton {
  readonly saved = signal(false);

  save() {
    this.saved.set(true);
    setTimeout(() => this.saved.set(false), 1500);
  }
}

Living state presets work the same way: <gf-icon name="bell" animate="shake" trigger="state" [active]="hasNews()" /> swings the bell while the signal is true, and animate="spin" on loader-circle makes a spinner. The animations are CSS inside the svg and stop under reduced motion; the animated icons guide lists every preset and trigger.

Inputs

Every shared prop is an input with the same name and meaning as in the other glyphfox packages.

InputTypeDefaultDescription
sizenumber, string24Width and height; numbers are pixels.
colorstringcurrentColorMain color.
secondaryColorstringcolorColor of the duotone layer and of the solid fill.
secondaryOpacitynumber0.3 / 1Opacity of the duotone layer or of the solid fill.
weightnumber2Stroke from 1 to 3 on the 24 grid, optically compensated below 24 px.
absoluteWeightbooleanfalseKeeps the stroke at weight pixels at any size.
cornersround, sharproundRound or sharp joins and caps.
variantoutline, duotone, solidoutlineStyle axis; solid comes from the closed shapes.
rotatenumber0Rotation in degrees.
flipXbooleanfalseMirror horizontally.
flipYbooleanfalseMirror vertically.
mirrorRtlbooleanfalseMirror in right to left layouts.
animatedraw, pop, wiggle, spin, pulse, bounce, shakenoneLiving state preset.
triggermount, hover, click, statemountWhen the animation or the morph plays.
activebooleanfalseThe state for trigger state.
durationnumber (ms)per presetAnimation or morph duration.
delaynumber (ms)0Animation or morph delay.
loopboolean, numberfalseRepeat forever or a number of times (spin and pulse loop by default).
modifierplus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dotnoneBadge with an automatic knockout gap.
modifierCornertop-left, top-right, bottom-left, bottom-rightbottom-rightCorner of the badge.
morphToname, datanoneMorph target; the trigger decides when it shows.
titlestringnoneAccessible name: title, role img and aria-label.
decorativebooleantrue without a titleHides the icon from assistive technology.

Besides the shared props, gf-icon takes name (any spec), icon (icon data), icons (2 to 10 parts of a fusion), layout and gap.

Pitfalls

provideGlyphFox in a component
It returns environment providers, which belong in the application config, an NgModule or a route. Angular rejects them in the providers of a component.
A lazy route loses the root defaults
Route providers replace the configuration for everything under the route, because multi providers are not merged across injectors. Spread a shared config object into every call.
aria-label on the host
An aria-label on gf-icon makes the inner svg non decorative (role="img"), but the label stays on the host element, which has no role, so screen readers may announce nothing. Use the title input for a meaningful icon, or label the parent button and keep the icon decorative.
A fusion that never appears
If one part of [icons] is not registered, the whole fusion waits for it and renders empty in the meantime, on the server included. Register every part.
Registering the whole set by accident
provideGlyphFox({ icons }) with the full record from glyphfox keeps every icon in the bundle. Import the named data exports you need instead.

Questions

Can I use glyphfox-angular in an NgModule based app?

Yes. GlyphFoxIcon is a standalone component, so add it to the imports of the NgModule that declares your components, and put provideGlyphFox(...) in the providers of your root module.

Which Angular versions are supported?

Angular 17 and newer, the peer dependency range of the package. On 17 and 18, keep standalone: true on the components that import GlyphFoxIcon; from 19 it is the default. Self closing tags such as <gf-icon name="home" /> work in every supported version.

Icons in this guide