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
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.
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
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.
<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.
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.
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;
import { Routes } from '@angular/router'; export const routes: Routes = [ { path: 'billing', loadChildren: () => import('./billing/billing.routes').then((m) => m.BILLING_ROUTES) }, ];
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()".
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.
| Input | Type | Default | Description |
|---|---|---|---|
size | number, string | 24 | Width and height; numbers are pixels. |
color | string | currentColor | Main color. |
secondaryColor | string | color | Color of the duotone layer and of the solid fill. |
secondaryOpacity | number | 0.3 / 1 | Opacity of the duotone layer or of the solid fill. |
weight | number | 2 | Stroke from 1 to 3 on the 24 grid, optically compensated below 24 px. |
absoluteWeight | boolean | false | Keeps the stroke at weight pixels at any size. |
corners | round, sharp | round | Round or sharp joins and caps. |
variant | outline, duotone, solid | outline | Style axis; solid comes from the closed shapes. |
rotate | number | 0 | Rotation in degrees. |
flipX | boolean | false | Mirror horizontally. |
flipY | boolean | false | Mirror vertically. |
mirrorRtl | boolean | false | Mirror in right to left layouts. |
animate | draw, pop, wiggle, spin, pulse, bounce, shake | none | Living state preset. |
trigger | mount, hover, click, state | mount | When the animation or the morph plays. |
active | boolean | false | The state for trigger state. |
duration | number (ms) | per preset | Animation or morph duration. |
delay | number (ms) | 0 | Animation or morph delay. |
loop | boolean, number | false | Repeat forever or a number of times (spin and pulse loop by default). |
modifier | plus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dot | none | Badge with an automatic knockout gap. |
modifierCorner | top-left, top-right, bottom-left, bottom-right | bottom-right | Corner of the badge. |
morphTo | name, data | none | Morph target; the trigger decides when it shows. |
title | string | none | Accessible name: title, role img and aria-label. |
decorative | boolean | true without a title | Hides 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
providersof 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-labelongf-iconmakes 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 thetitleinput 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 fromglyphfoxkeeps 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.