I am developing a custom icon library for my Angular project, as many of the icons in Font Awesome now are not free. I used Google Material Icons, implementing them as a stylesheet. Then I created an icon servcie and an icon directive for easy calling.
Here is my icon.service.ts:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class IconsService {
constructor(private http: HttpClient, private sanitizer: DomSanitizer) {}
getIcon(name: string): Observable<SafeHtml> {
return this.http.get(`assets/icons/${name}.svg`, { responseType: 'text' }).pipe(
map(svgString => this.sanitizer.bypassSecurityTrustHtml(svgString) as SafeHtml) // assert non-null
);
}
}
My icon directive where most work gets done:
import {Directive, ElementRef, Input, Renderer2} from '@angular/core';
@Directive({
selector: '[icon]',
standalone: true,
})
export class IconDirective {
private _iconName: string = '';
private _spin: boolean = false;
private _size: string | undefined;
private _flipHorizontal: boolean = false;
private _flipVertical: boolean = false;
private _color: string | undefined;
@Input('icon')
set icon(value: string) {
const parts = value.split(' ');
this._iconName = parts[0];
this._spin = parts.includes('spin');
this._flipHorizontal = parts.includes('flip-horizontal');
this._flipVertical = parts.includes('flip-vertical');
this._size = parts.find(part => part.match(/^[0-5]x$/)) || '1x'; // Default to 1x
const colorPart = parts.find(part => part.startsWith('color-'));
this._color = colorPart ? colorPart.split('-')[1] : undefined;
this.updateIcon();
}
constructor(private el: ElementRef, private renderer: Renderer2) {
}
ngAfterViewInit(): void {
this.setupLazyLoading();
}
private setupLazyLoading(): void {
const options = {
root: null, // observes changes in the viewport
threshold: 0.01, // trigger when 1% of the element is visible
};
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.updateIcon();
observer.unobserve(entry.target); // Stop observing once loaded
}
});
}, options);
observer.observe(this.el.nativeElement);
}
private updateIcon(): void {
console.log('Updating icon');
// Clear previous classes
this.el.nativeElement.className = '';
// Set text content to the icon name
this.renderer.setProperty(this.el.nativeElement, 'textContent', this._iconName);
this.renderer.addClass(this.el.nativeElement, 'material-icons');
// Apply size, spin, and flip classes
if (this._spin) {
this.renderer.addClass(this.el.nativeElement, 'spin');
}
if (this._flipHorizontal) {
this.renderer.addClass(this.el.nativeElement, 'flip-horizontal');
}
if (this._flipVertical) {
this.renderer.addClass(this.el.nativeElement, 'flip-vertical');
}
if (this._size) {
this.renderer.addClass(this.el.nativeElement, `icon-${this._size}`);
}
if (this._color) {
this.renderer.setStyle(this.el.nativeElement, 'color', this._color);
}
}
}
My html at this point:
<h1>Icon examples</h1>
<h2>Sizing: <i icon="search"></i><i icon="search 2x"></i><i icon="search 3x"></i></h2>
<h2>Spinning: <i icon="search spin"></i></h2>
<h2>Flipping horizontally and vertically: <i icon="search flip-horizontal"></i><i icon="search flip-vertical"></i></h2>
<h2>Colors: <i icon="search color-blue"></i><i icon="search color-red"></i><i icon="search color-green"></i></h2>
<h1>Button example: <button><i icon="donut_large spin"></i>Loading</button></h1>
<button><i icon="settings 2x"></i></button>
<button><i icon="menu 2x"></i></button>
<!--<button><i icon="stacks 2x"></i></button>-->
<button><i icon="print 2x"></i></button>
<button><i icon="open_with 2x"></i></button>
<button><i icon="add 2x"></i></button>
<button><i icon="public 2x"></i></button>
<!--<button><i icon="captive_portal 2x"></i></button>-->
<button><i icon="filter_alt 2x"></i></button>
<button><i icon="filter_alt_off 2x"></i></button>
<button><i icon="arrow_forward_ios 2x"></i></button>
<button><i icon="arrow_back_ios 2x"></i></button>
<button><i icon="pan_tool 2x"></i></button>
<button><i icon="progress_activity 2x"></i></button>
But this is what I can see on screen, some icons like progress activity are not rendering, what am I doing wrong?
I tried changing browser, changing my css to include font family.. Nothing.
/*Icon base styles */
[icon] {
display: inline-block;
font-size: 16px;
padding: 2px;
fill-opacity: 0;
vertical-align: middle;
horiz-align: center;
}
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(https://example.com/MaterialIcons-Regular.eot); /* For IE6-8 */
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url(https://example.com/MaterialIcons-Regular.woff2) format('woff2'),
url(https://example.com/MaterialIcons-Regular.woff) format('woff'),
url(https://example.com/MaterialIcons-Regular.ttf) format('truetype');
}
/* Icon size variations */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.spin {
animation: spin 2s infinite linear;
padding: 2px;
}
.flip-horizontal {
transform: scaleX(-1);
}
.flip-vertical {
transform: scaleY(-1);
}
.flip-left {
transform: rotateY(180deg);
}
.flip-right {
transform: rotateY(-180deg);
}
.material-icons.icon-1x { font-size: 16px; } /* Default Material Icons size */
.material-icons.icon-2x { font-size: 32px; }
.material-icons.icon-3x { font-size: 48px; }
.material-icons.icon-4x { font-size: 64px; }
I can see that [progress_activity](https://fonts.google.com/icons?icon.query=progress_activity)
is in the Material Symbols collection but not in the Material Icons collection.
My initial guess is that you followed the instructions to install Material Icons and if you want additional iconography like progress_activity
you should follow the directions for Material Symbols instead.