Skip to content

Instantly share code, notes, and snippets.

@jbjhjm
Created July 8, 2024 08:31
Show Gist options
  • Save jbjhjm/5e9c522c98314ec2c9abf6cf420c5776 to your computer and use it in GitHub Desktop.
Save jbjhjm/5e9c522c98314ec2c9abf6cf420c5776 to your computer and use it in GitHub Desktop.
minimal-size replacement for fontawesome angular fa-icon component

angular-fontawesome is a pretty huge lib as a result of bundling many - possibly useful - features. In react-fontawesome one can find an ongoing discussion of offering a more compact solution to anyone only needing basic functionality.

This gist showcases a solution based on modern angular using signals + standalone component. It is a minimal replacement for the entire angular-fontawesome UI library.

Opposed to other solutions showcased in discussion thread, this component provides SOME configurability:

  • icon sizes sm to 4x supported
  • fixedWidth supported
  • icon spinning supported
import type { IconDefinition } from '@fortawesome/fontawesome-common-types';
import { NgIf } from '@angular/common';
import { Component, computed, input } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { UiIconLibrary } from './icons-library';
/**
* a custom lightweight implementation of fa-icon component.
* https://github.com/FortAwesome/react-fontawesome/issues/232#issuecomment-115
* https://github.com/FortAwesome/angular-fontawesome/blob/main/src/lib/icon/icon.component.ts
*/
@Component({
standalone: true,
selector: 'ui-icon',
template: `
<svg *ngIf="icon() let ico" [class]="classList()"
[attr.viewBox]="'0 0 ' + ico.icon[0] + ' ' + ico.icon[1]"
role="img" xmlns="http://www.w3.org/2000/svg"
[innerHTML]="makeSvg(ico)"
></svg>
`,
styles: [`
svg:not(:root).svg-inline--fa,
svg:not(:host).svg-inline--fa { overflow: visible; box-sizing: content-box; }
.svg-inline--fa.fa-fw { width: var(--fa-fw-width, 1.25em);}
.fa-fw { text-align: center; width: 1.25em; }
.svg-inline--fa { display: var(--fa-display, inline-block); height: 1em; overflow: visible; vertical-align: -0.125em; }
.fa-xs { font-size: 0.75em; line-height: 0.08333em; vertical-align: 0.125em; }
.fa-sm { font-size: 0.875em; line-height: 0.07143em; vertical-align: 0.05357em; }
.fa-lg { font-size: 1.25em; line-height: 0.05em; vertical-align: -0.075em; }
.fa-xl { font-size: 1.5em; line-height: 0.04167em; vertical-align: -0.125em; }
.fa-2x { font-size: 2em; }
.fa-3x { font-size: 3em; }
.fa-4x { font-size: 4em; }
.svg-inline--fa.fa-xs { vertical-align: 0em; }
.svg-inline--fa.fa-sm { vertical-align: -0.07143em; }
.svg-inline--fa.fa-lg { vertical-align: -0.2em; }
.svg-inline--fa.fa-xl { vertical-align: -0.25em; }
.svg-inline--fa.fa-2xl { vertical-align: -0.3125em; }
@keyframes fa-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.fa-spin { animation-name: fa-spin;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 2s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, linear); }
`],
imports: [NgIf],
})
export class UiIconComponent {
iconInput = input<IconDefinition | string | string[]>(null,{alias:'icon'})
icon = computed<IconDefinition | null>(()=>{
const input = this.iconInput();
if(Array.isArray(input)) return this.iconLib.getIconDefinition(input[0] as any,input[1] as any);
if(typeof input === 'object') return input;
if(typeof input === 'string') return this.iconLib.getIconDefinition('fas',input as any);
return null
})
size = input<"lg" | "sm" | "1x" | "2x" | "3x" | "4X">("1x")
fixedWidth = input<boolean>(false)
spin = input<boolean>(false)
classList = computed<string>(()=>{
let list = 'svg-inline--fa';
if(this.size()) list += ` fa-${this.size()}`
if(this.fixedWidth()) list += ` fa-fw`
if(this.spin()) list += ` fa-spin`
return list
})
constructor(private iconLib:UiIconLibrary, private sanitizer: DomSanitizer) {}
protected makeSvg(def: IconDefinition) {
const { icon } = def;
if(!Array.isArray(icon)) {
console.log(def)
throw new Error('invalid icon definition!')
}
// const [width, height, ligatures, unicode, svgPathData] = icon;
const svgPathData = icon[4];
const content = Array.isArray(svgPathData)
? `<g>${svgPathData.map((x) => `<path d="${x}" />`)}</g>`
: `<path fill="currentColor" d="${svgPathData}" />`;
return this.sanitizer.bypassSecurityTrustHtml(content);
}
}
/**
* minimal version of https://github.com/FortAwesome/angular-fontawesome/blob/main/src/lib/icon-library.ts
* No functionality to add a whole icon library at once
* Copyright (c) 2018 Fonticons, Inc. and contributors
* MIT License
*/
import { Injectable } from '@angular/core';
import type { IconDefinition, IconName, IconPrefix } from '@fortawesome/fontawesome-common-types';
@Injectable({ providedIn: 'root' })
export class UiIconLibrary {
private definitions: { [prefix: string]: { [name: string]: IconDefinition } } = {};
addIcons(...icons: IconDefinition[]) {
for (const icon of icons) {
if (!(icon.prefix in this.definitions)) {
this.definitions[icon.prefix] = {};
}
this.definitions[icon.prefix][icon.iconName] = icon;
for (const alias of icon.icon[2]) {
if (typeof alias === 'string') {
this.definitions[icon.prefix][alias] = icon;
}
}
}
}
getIconDefinition(prefix: IconPrefix, name: IconName): IconDefinition | null {
if (prefix in this.definitions && name in this.definitions[prefix]) {
return this.definitions[prefix][name];
}
return null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment