Platform Guides

Angular 18 Favicon Guide: Configuration and Best Practices

Step-by-step guide to adding favicons in Angular 18, including angular.json configuration and asset management.

6 min read
Free Guide

Angular 18 Favicons: Configuration Over Convention

Angular takes a different approach to favicons than its counterparts. While React says "just drop it in public," Angular says "declare it in your configuration." This might seem like extra work, but it offers precise control over asset handling, build optimisation, and multi-project workspaces.

This guide covers Angular 18's approach, including the latest features like improved build performance and the new application builder. If you're migrating from older Angular versions, pay attention to the configuration changes.

The Angular Way: angular.json Configuration

Basic Favicon Setup

Your Angular project structure:

my-angular-app/
├── src/
│   ├── favicon.ico
│   └── index.html
├── angular.json
└── package.json

In angular.json, ensure your favicon is included in assets:

{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "options": {
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ]
          }
        }
      }
    }
  }
}

Reference it in src/index.html:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>My Angular App</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>

Multiple Favicon Sizes and Types

For comprehensive device support:

Step 1: Add Favicon Files

src/
├── favicon.ico
├── favicon-16x16.png
├── favicon-32x32.png
├── apple-touch-icon.png
├── android-chrome-192x192.png
├── android-chrome-512x512.png
└── site.webmanifest

Step 2: Update angular.json

{
  "assets": [
    "src/favicon.ico",
    "src/favicon-16x16.png",
    "src/favicon-32x32.png",
    "src/apple-touch-icon.png",
    "src/android-chrome-192x192.png",
    "src/android-chrome-512x512.png",
    "src/site.webmanifest",
    "src/assets"
  ]
}

Step 3: Update index.html

<head>
  <!-- Basic favicon -->
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  
  <!-- PNG favicons -->
  <link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png">
  
  <!-- Apple Touch Icon -->
  <link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
  
  <!-- Android Chrome Icons -->
  <link rel="icon" type="image/png" sizes="192x192" href="android-chrome-192x192.png">
  <link rel="icon" type="image/png" sizes="512x512" href="android-chrome-512x512.png">
  
  <!-- Web App Manifest -->
  <link rel="manifest" href="site.webmanifest">
</head>

Asset Copying with Glob Patterns

Angular 18's improved asset handling allows sophisticated copying:

{
  "assets": [
    {
      "glob": "**/*",
      "input": "src/favicons/",
      "output": "/"
    },
    {
      "glob": "favicon.ico",
      "input": "src/",
      "output": "/"
    }
  ]
}

This approach keeps favicons organised in a dedicated folder.

Dynamic Favicons in Angular

Service for Favicon Management

// favicon.service.ts
import { Injectable, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';

@Injectable({
  providedIn: 'root'
})
export class FaviconService {
  constructor(@Inject(DOCUMENT) private document: Document) {}

  setFavicon(href: string): void {
    const link: HTMLLinkElement = this.document.querySelector("link[rel*='icon']") || 
                                 this.document.createElement('link');
    link.type = 'image/x-icon';
    link.rel = 'shortcut icon';
    link.href = href;
    
    this.document.head.appendChild(link);
  }

  setMultipleFavicons(favicons: { href: string; sizes?: string; type?: string }[]): void {
    // Remove existing favicons
    const existingLinks = this.document.querySelectorAll("link[rel*='icon']");
    existingLinks.forEach(link => link.remove());

    // Add new favicons
    favicons.forEach(favicon => {
      const link = this.document.createElement('link');
      link.rel = 'icon';
      link.href = favicon.href;
      if (favicon.type) link.type = favicon.type;
      if (favicon.sizes) link.sizes = favicon.sizes;
      
      this.document.head.appendChild(link);
    });
  }
}

Usage in Components

// app.component.ts
import { Component, OnInit } from '@angular/core';
import { FaviconService } from './services/favicon.service';

@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>'
})
export class AppComponent implements OnInit {
  constructor(private faviconService: FaviconService) {}

  ngOnInit(): void {
    // Set favicon based on environment
    const favicon = environment.production ? 'favicon.ico' : 'favicon-dev.ico';
    this.faviconService.setFavicon(favicon);
  }
}

Notification Badge Example

// notification.service.ts
@Injectable({
  providedIn: 'root'
})
export class NotificationService {
  private canvas: HTMLCanvasElement;
  private ctx: CanvasRenderingContext2D;

  constructor(private faviconService: FaviconService) {
    this.canvas = document.createElement('canvas');
    this.canvas.width = 32;
    this.canvas.height = 32;
    this.ctx = this.canvas.getContext('2d')!;
  }

  updateFaviconBadge(count: number): void {
    const img = new Image();
    img.onload = () => {
      // Clear canvas
      this.ctx.clearRect(0, 0, 32, 32);
      
      // Draw original favicon
      this.ctx.drawImage(img, 0, 0, 32, 32);
      
      if (count > 0) {
        // Draw red circle
        this.ctx.fillStyle = '#ff0000';
        this.ctx.beginPath();
        this.ctx.arc(24, 8, 8, 0, 2 * Math.PI);
        this.ctx.fill();
        
        // Draw count
        this.ctx.fillStyle = '#ffffff';
        this.ctx.font = 'bold 11px Arial';
        this.ctx.textAlign = 'center';
        this.ctx.textBaseline = 'middle';
        this.ctx.fillText(count > 99 ? '99+' : count.toString(), 24, 8);
      }
      
      // Update favicon
      this.faviconService.setFavicon(this.canvas.toDataURL('image/png'));
    };
    
    img.src = 'favicon-32x32.png';
  }
}

PWA Configuration

For Progressive Web App support:

manifest.json

{
  "name": "My Angular App",
  "short_name": "AngularApp",
  "theme_color": "#1976d2",
  "background_color": "#fafafa",
  "display": "standalone",
  "scope": "./",
  "start_url": "./",
  "icons": [
    {
      "src": "android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "android-chrome-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Angular Service Worker

ng add @angular/pwa

This automatically:

  • Adds service worker support
  • Creates manifest.json
  • Updates index.html with PWA meta tags
  • Configures icon assets

Environment-Specific Favicons

Different favicons for dev/staging/production:

Configure Environments

// environments/environment.ts
export const environment = {
  production: false,
  faviconPath: 'favicon-dev.ico'
};

// environments/environment.prod.ts
export const environment = {
  production: true,
  faviconPath: 'favicon.ico'
};

File Replacements

In angular.json:

{
  "fileReplacements": [
    {
      "replace": "src/environments/environment.ts",
      "with": "src/environments/environment.prod.ts"
    },
    {
      "replace": "src/favicon-dev.ico",
      "with": "src/favicon.ico"
    }
  ]
}

Angular 18 Build Optimisations

The new application builder in Angular 18 handles favicons more efficiently:

{
  "architect": {
    "build": {
      "builder": "@angular-devkit/build-angular:application",
      "options": {
        "outputPath": "dist/my-app",
        "index": "src/index.html",
        "browser": "src/main.ts",
        "polyfills": ["zone.js"],
        "tsConfig": "tsconfig.app.json",
        "assets": [
          {
            "glob": "**/*",
            "input": "public",
            "output": "/"
          }
        ]
      }
    }
  }
}

Testing Favicon Implementation

Development Server

ng serve
# Check: http://localhost:4200/favicon.ico

Production Build

ng build
# Check dist folder for favicon files

E2E Testing

// favicon.e2e-spec.ts
import { browser, by, element } from 'protractor';

describe('Favicon Tests', () => {
  it('should have favicon link in head', async () => {
    await browser.get('/');
    const favicon = element(by.css('link[rel="icon"]'));
    expect(await favicon.isPresent()).toBe(true);
    expect(await favicon.getAttribute('href')).toContain('favicon.ico');
  });
});

Common Issues and Solutions

Favicon Not Updating?

  1. 1Clear Angular cache:

```bash

rm -rf .angular/cache

ng serve

```

  1. 1Check output path:

Ensure favicon is copied to dist folder

  1. 1Browser cache:

Add version query: href="favicon.ico?v=2"

Works Locally, Not in Production?

Check base href configuration:

<!-- For subdirectory deployment -->
<base href="/my-app/">

Or build with base href:

ng build --base-href /my-app/

Creating Favicons for Angular

Need all these favicon sizes? Use Unwrite's Favicon Generator. Upload your logo and get a complete favicon package ready for Angular, including all sizes and the manifest.json file. Everything processes privately in your browser.

Angular Favicon Checklist

  • [ ] Add favicon.ico to src folder
  • [ ] Configure assets in angular.json
  • [ ] Include multiple sizes for devices
  • [ ] Add apple-touch-icon for iOS
  • [ ] Create site.webmanifest for PWA
  • [ ] Test in production build
  • [ ] Consider environment-specific icons
  • [ ] Implement FaviconService for dynamic needs

The Angular approach might require more configuration than dropping a file in a folder, but it provides the control and consistency that large applications need. Embrace the configuration - it's there to help you build better apps.