Working with SVG icons in Angular projects can be approached in several ways, each with distinct trade-offs. Let's explore the evolution from basic implementations to an elegant, semantic solution.
The img
Tag Approach
The simplest method uses a standard img
tag:
<img src="assets/icons/my-icon.svg" alt="My Icon">
This approach offers automatic browser caching, but transforms your SVG into a raster image. You lose the ability to style the icon dynamically—changing colors, applying CSS transforms, or responding to hover states becomes impossible.
Using mat-icon
and Similar Libraries
Angular Material's mat-icon
provides a more integrated solution:
<mat-icon svgIcon="my-icon"></mat-icon>
While functional, this approach introduces complexity. Each icon requires an HTTP request to load and inline the SVG content. Additionally, you're wrapping your SVG with extra DOM elements, creating unnecessary nesting that can complicate styling and impact performance when using many icons.
SVG Sprites: The Traditional Performance Solution
SVG sprites bundle multiple icons into a single file, reducing HTTP requests and improving performance:
<!-- sprite.svg -->
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<symbol id="star" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</symbol>
<symbol id="heart" viewBox="0 0 24 24">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
</symbol>
</defs>
</svg>
Usage in templates:
<svg class="icon">
<use href="assets/sprite.svg#star"></use>
</svg>
Advantages:
- Single HTTP request for all icons
- Good browser caching
- Maintains SVG styling capabilities
Disadvantages:
- Manual sprite generation and maintenance
- All icons loaded regardless of usage (no tree-shaking)
- Icon identification still relies on string IDs
Direct Template Embedding
The third approach involves copying SVG code directly into your templates:
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
This method works excellently for styling and performance, but creates two significant challenges:
Icon Identification Issues
When reviewing code, it's impossible to determine which icon an SVG represents without visual inspection or reverse-engineering the path data.
Code Duplication
Reusing the same icon across multiple components leads to duplicated SVG markup, violating the DRY principle and making updates cumbersome.
Component-Based Solution: Close, But Not Perfect
Creating dedicated components for each icon solves the identification and duplication problems:
@Component({
selector: 'app-star-icon',
template: `
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
`
})
export class StarIcon {}
This approach provides semantic naming and leverages Angular's tree-shaking to exclude unused icons from your bundle. However, it introduces a new problem: the host element.
The Host Element Challenge
Component-based icons create an additional DOM layer:
<!-- What you write -->
<app-star-icon class="text-blue-500 size-6"></app-star-icon>
<!-- What gets rendered -->
<app-star-icon class="text-blue-500 size-6">
<svg viewBox="0 0 24 24">...</svg>
</app-star-icon>
This host element complicates CSS styling. Direct child selectors don't work as expected, and you often need to use CSS tricks to bypass the wrapper element.
The Elegant Solution: Attribute Selectors
Angular's attribute selector feature provides the perfect solution. Instead of using element selectors, we can create components with attribute selectors:
@Component({
selector: 'svg[si-star-icon]',
template: `
<svg:path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
`,
host:{
'[attr.viewBox]': 'viewBox()',
}
})
export class StarIcon {
readonly viewBox = input<string>('0 0 24 24');
}
Now you can use icons like this:
<svg class="text-blue-500 size-6" si-star-icon></svg>
This approach combines the best of all worlds:
- Semantic naming: Clear icon identification
- No host element: Direct SVG styling
- No duplication: Reusable components
- Tree-shaking: Unused icons are excluded
- Performance: No HTTP requests
The Time Investment Challenge
Creating attribute selector components for every SVG icon requires significant setup time and maintenance effort. You need to convert each icon, create components, and manage the collection.
Introducing @semantic-icons
To solve the time investment problem, I've created @semantic-icons—a comprehensive collection of popular icon libraries pre-converted for Angular attribute selector usage.
The project includes icons from:
- Lucide Icons
- Heroicons
- Tabler Icons
- And many more
Usage Example
Want to use the bird icon from Lucide? Simply:
<svg class="text-blue-500 size-6" si-bird-icon></svg>
The naming convention follows a simple pattern: si-{icon-name}-icon
Getting Started
Visit the project repository: https://github.com/khalilou88/semantic-icons
The library provides thousands of ready-to-use SVG icons that integrate seamlessly with your Angular projects, requiring zero configuration and offering maximum styling flexibility.
Conclusion
SVG icon implementation in Angular has evolved from simple image tags to sophisticated, semantic solutions. The attribute selector approach with @semantic-icons
represents the current best practice, offering developers a powerful, efficient, and maintainable way to work with SVG icons.
This solution eliminates the common pain points of icon management while preserving all the benefits of inline SVG usage. Give it a try in your next Angular project—your future self will thank you.
Top comments (0)