How To Add Font Awesome in Angular?

Angular Font Awesome

Icons play a crucial role in building modern and user-friendly web applications. Font Awesome is one of the most popular icon libraries, and Angular provides multiple clean ways to integrate it.

In this article, you’ll learn all the correct ways to add Font Awesome in Angular, when to use each approach, and common mistakes to avoid.

Prerequisites

Before starting, make sure:

  • The Angular project is already created
  • Node.js and npm are installed
  • Basic Angular knowledge (components & templates)

Method 1: Add Font Awesome Using CDN.

Font Awesome CDN method works by loading Font Awesome’s CSS file directly from the internet into your Angular app. This is the fastest method and best for:
  • Demos
  • Small projects
  • Prototypes
Step 1: Add Font Awesome CDN to the index.html page in Angular.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>AngularApp</title>

  <!-- Font Awesome CDN -->
  <link
    rel="stylesheet"
    href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
  />
</head>
<body>
  <app-root></app-root>
</body>
</html>

Step 2: Use Icons in Component HTML.
<i class="fa-solid fa-user"></i>
<i class="fa-solid fa-envelope"></i>
<i class="fa-solid fa-lock"></i>

Screenshot of font awesome icons
Pros
  • No installation required
  • Very easy setup
Cons
  • External dependency
  • Not ideal for production or offline builds

Method 2: Install Font Awesome via npm.

This is the most commonly used and professional approach.

Step 1: Install Font Awesome. 
npm install @fortawesome/fontawesome-free

Step 2: Add CSS to angular.json
"styles": [
  "node_modules/@fortawesome/fontawesome-free/css/all.min.css",
  "src/styles.css"
]

Note: Always add Font Awesome before your custom styles.

Step 3: Restart the Angular Server.
ng serve

Step 4: Use Icons in Template.
<i class="fa-solid fa-user"></i>
<i class="fa-regular fa-bell"></i>
<i class="fa-brands fa-github"></i>

screenshot for font awesome
Pros
  • Works offline
  • Version-controlled
  • Production-ready
Cons
  • Loads entire icon set (slightly larger bundle)
Adding Font Awesome in Angular is simple once you understand which approach fits your use case.
Start with npm-based CSS, and move to Angular Font Awesome for scalable applications.

What is CTE in SQL?

CTE (Common Table Expression) is a temporary named result set that you can use inside a single SQL statement to make complex queries more readable, reusable, and maintainable.

A CTE is defined using the WITH keyword.

We use CTE:

  • To simplify complex queries
  • To avoid repeating subqueries
  • To handle hierarchical or recursive data
  • To improve query readability.
Basic Syntax:
WITH cte_name AS (
    SELECT columns
    FROM table
    WHERE condition
)
SELECT *
FROM cte_name;

Simple Example: Find employees earning more than the average salary
WITH AvgSalaryCTE AS (
    SELECT AVG(Salary) AS AvgSalary
    FROM Employee
)
SELECT *
FROM Employee
WHERE Salary > (SELECT AvgSalary FROM AvgSalaryCTE);
Explanation:
  • AvgSalaryCTE calculates the average salary.
  • The main query uses that result to filter employees.
  • CTE exists only during this query execution.

How is CTE better than a subquery?

A CTE (Common Table Expression) is often better than a subquery because it improves readability, reusability, and maintainability, especially in complex SQL queries.

Example: Using Subquery.
SELECT *
FROM Employee
WHERE Salary > (
    SELECT AVG(Salary)
    FROM Employee
);

Example: Using CTE.
WITH AvgSalaryCTE AS (
    SELECT AVG(Salary) AS AvgSalary
    FROM Employee
)
SELECT *
FROM Employee
WHERE Salary > (SELECT AvgSalary FROM AvgSalaryCTE);

Note: CTE separates logic into named blocks, making queries easier to understand.

Recursive CTE.

A Recursive CTE (Common Table Expression) is a CTE that refers to itself to process hierarchical or tree-structured data (parent → child relationships).

It repeatedly executes until no more rows are returned.

Example: Employee–Manager Hierarchy.
WITH EmpHierarchy AS (
    -- Anchor Query (Top-Level Manager)
    SELECT 
        EmpId,
        Name,
        ManagerId,
        0 AS Level
    FROM Employee
    WHERE ManagerId IS NULL

    UNION ALL

    -- Recursive Query (Subordinates)
    SELECT 
        e.EmpId,
        e.Name,
        e.ManagerId,
        h.Level + 1
    FROM Employee e
    INNER JOIN EmpHierarchy h
        ON e.ManagerId = h.EmpId
)
SELECT * FROM EmpHierarchy;
Output:
EmpId | Name   | ManagerId | Level
1     | CEO    | NULL      | 0
2     | Manager| 1         | 1
3     | Dev    | 2         | 2
4     | Tester | 2         | 2

Use a recursive CTE when the data is hierarchical:
  • Employee–Manager structure
  • Category–Subcategory
  • Organization chart
  • Folder–Subfolder
  • Tree or graph traversal
  • Bill of materials
CTE is better than a subquery because it improves readability, avoids repeated logic, supports recursion, and makes complex SQL queries easier to maintain, while performance remains similar.

Note: A CTE (Common Table Expression) is temporary and exists only for the very next SQL statement that follows the WITH clause. Once that statement finishes execution, the CTE is gone.

Example: Invalid Use of CTE.

WITH EmpCTE AS (
    SELECT * FROM Employee
)
SELECT * FROM EmpCTE;

SELECT COUNT(*) FROM EmpCTE;  -- ❌ ERROR
A CTE can be referenced multiple times within a single SQL statement, but it cannot be used across multiple SELECT statements.

Singleton Design Pattern n C#.

The Singleton Design Pattern is one of the simplest yet most important creational design patterns in software engineering. It ensures that only one instance of a class exists in memory throughout the application's lifetime and provides a global point of access to that instance.

Design Pattern

What is the Singleton Design Pattern?

A Singleton class allows only one object of itself to be created and shared across the entire application. This pattern is extremely useful when you need to:

  • Manage shared resources (like logging, configuration, cache, or thread pool).
  • Avoid duplicate object creation for performance reasons.
  • Maintain a single source of truth.
Think of a printer spooler or Windows Task Manager. You can open it multiple times, but under the hood, only one instance manages all print jobs or processes. That’s a Singleton in action, one object serving the whole system.

Does who don't know: The printer spooler service is a Windows service that manages and queues print jobs for your printer.

Key Characteristics 
  • Only one instance of the class is created. 
  • Global access to that instance through a public static property or method. 
  • Thread-safety is ensured in multi-threaded environments. 
  • Lazy initialization means the object is created only when needed.

How To Implement the Singleton Design Pattern in C#?

There are multiple ways to implement the Singleton design pattern in C#, and here we are going to discuss all, and at the end will decide which one is best and easiest to implement.

Approach 1: Non-Thread Safe Implementation.

This simple Singleton creates only one instance of the Singleton class. It uses a private static field _instance to store that single object. The constructor is private, so no one can create the object using new. The GetInstance() method checks if it _instance is null — if yes, creates the object; if not, returns the existing one.

Example Code:
public class Singleton
{
    private static Singleton? _instance;

    private Singleton() { }

    public static Singleton GetInstance()
    {
        if (_instance == null)
            _instance = new Singleton();
        return _instance;
    }

    public void Message(string message)
    {
        Console.WriteLine($"{DateTime.Now}: {message}");
    }
}
Using a Singleton Class in a Program.cs
//Singleton Design
var instance1 = Singleton.GetInstance();
var instance2 = Singleton.GetInstance();

if(instance1 == instance2) Console.WriteLine("True- Both refer to same instance.");

instance1.Message("Log from Instance 1");
instance2.Message("Log from Instance 2");
Output:
True- Both refer to same instance.
28-11-2025 16:39:40: Log from Instance 1
28-11-2025 16:39:40: Log from Instance 2

This version isn’t thread-safe. If two threads call GetInstance() Simultaneously, two objects could be created. Let's understand another approach to make our code thread-safe safe so only one instance is possible because that is our goal in a singleton.

Approach 2: Thread-Safe Singleton.

A Thread-Safe Singleton ensures that only one instance of a class is created, even when multiple threads try to create it at the same time. It prevents race conditions and guarantees that all threads receive the same shared instance safely.

Example Code:
public class Singleton
{
    private static Singleton? _instance;
    private static readonly object _lock = new object();

    //private constructor to prevents extension instantiation
    private Singleton()
    {
        Console.WriteLine("Instance Created.");
    }

    public static Singleton GetInstance()
    {
        //1st check without lock
        if(_instance == null)
        {
            lock (_lock) //only one thread can enter at a time
            {
                //2nd check with lock
                if(_instance == null)
                {
                    _instance = new Singleton();
                }
            }
        }
        return _instance;
    }
}

When using a non-thread-safe Singleton, two threads can reach the line if(_instance == null) at the same time. Both threads see that the instance is null and both try to create the object. In double-checked locking, Thread A enters the lock first and creates the Singleton instance, while Thread B waits outside the lock.

Once Thread A finishes creating the instance and leaves the lock, Thread B enters the lock. If we didn’t check _instance again inside the lock, Thread B would also create a new instance, overwriting the first one.

Multiple Threads Calling It:
Thread t1 = new Thread(() =>
{
    var s1 = Singleton.GetInstance();
});

Thread t2 = new Thread(() =>
{
    var s2 = Singleton.GetInstance();
});

t1.Start();
t2.Start();

t1.Join();
t2.Join();
Output:
Instance Created.

Even with 2 (or 100) threads, the constructor runs only once.

The above approach solves my multiple-thread problem, but it is a lot of code to write, and handling threads manually is not an easy job. So, .NET provides a better solution to implement Singleton more easily and effectively. Let's discuss that now.

Approach 3: Singleton Using .NET Lazy<T>

Lazy<T> is a special .NET class that automatically handles lazy initialization + thread safety for you. It ensures the Singleton object is created only when accessed, and only once, even if multiple threads call it at the same time.

Does Who Don't KnowLazy initialization is a technique used to delay the creation of an object until it is actually needed. This can improve performance and reduce memory usage, especially for objects that are expensive to create or may not be used at all during the program's execution.

Example Code:
public sealed class Singleton
{
    // Lazy ensures thread-safe, lazy initialization
    private static readonly Lazy<Singleton> _instance =
        new Lazy<Singleton>(() => new Singleton());

    // Private constructor prevents external creation
    private Singleton()
    {
        Console.WriteLine("Singleton Instance Created");
    }

    // Public accessor to get the single instance
    public static Singleton Instance => _instance.Value;

    public void ShowMessage(string msg)
    {
        Console.WriteLine("Message: " + msg);
    }
}

Calling inside Program.cs
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Accessing Singleton First Time:");
        Singleton s1 = Singleton.Instance;
        s1.ShowMessage("Hello from First Instance");

        Console.WriteLine("\nAccessing Singleton Second Time:");
        Singleton s2 = Singleton.Instance;
        s2.ShowMessage("Hello from Second Instance");

        Console.WriteLine($"\nAre both instances same?  { (s1 == s2) }");
    }
}
Output:
Accessing Singleton First Time:
Singleton Instance Created
Message: Hello from First Instance

Accessing Singleton Second Time:
Message: Hello from Second Instance

Are both instances same?  True

This approach is fully thread-safe with cleaner code and no manual lock required.

When to Use Singleton Pattern

Use Singleton when:
  • You need exactly one instance for coordination (e.g., logging service, configuration manager).
  • Creating multiple instances would cause conflicts or inconsistency.
  • You need shared state or cache across different parts of the app.

When Not to Use Singleton

Avoid Singleton when:
  • Your class maintains mutable state - it can cause unexpected side effects.
  • It leads to tight coupling - other classes depend directly on the singleton.
  • It complicates unit testing (since global state can persist across tests).

The Singleton Design Pattern is powerful and useful but like all global patterns, it must be used wisely. Overusing it can lead to tight coupling and hidden dependencies. However, when applied correctly for shared resources or configuration objects it provides an elegant, thread-safe way to ensure a single source of truth across your application.

SOLID Principles Implementation in C#.

Software development is not just about writing code; it’s about writing clean, maintainable, scalable, and testable code. That’s exactly what the SOLID principles help us achieve.

Whether you’re building a small API or an enterprise-level microservice, understanding SOLID principles makes your code more flexible, easier to extend, and less prone to bugs.

In this article, we’ll break down SOLID principles with simple explanations and practical C# implementations.

SOLID Principle Full Form

What is the SOLID Principle?

The SOLID Principles are a set of five core object-oriented design (OOD) guidelines used in software development to create systems that are more understandable, flexible, maintainable, and scalable. Coined by Michael Feathers and based on principles from Robert C. Martin ("Uncle Bob").

SOLID is an acronym for five object-oriented design principles: 

  • S: Single Responsibility Principle 
  • O: Open/Closed Principle 
  • L: Liskov Substitution Principle 
  • I: Interface Segregation Principle 
  • D: Dependency Inversion Principle 

Let’s go through each principle one by one and look at the problems that arise when we don’t follow the SOLID Principle. After that, we’ll explore how applying these principles can help us write cleaner, more maintainable code.

1. S - Single Responsibility Principle.

The 'S' in the SOLID principles stands for the Single Responsibility Principle (SRP). The Single Responsibility Principle states that a class, module, or function should have only one reason to change, meaning it should have a single, clearly defined purpose or job within the software system.

Bad Example: Imagine an online shopping system where:

  • An order is created
  • Order is saved to the database
  • Customer is emailed

If all of this is in ONE class, it violates SRP.

Example Code:

public class OrderService
{
    public void CreateOrder()
    {
        Console.WriteLine("Order created.");
    }

    public void SaveToDatabase()
    {
        Console.WriteLine("Order saved to DB.");
    }

    public void SendEmail()
    {
        Console.WriteLine("Email sent to customer.");
    }
}

Let's see how we can write better code using SRP.

Good Example: To follow the single responsibility principle, we have created classes in such a way that each class has its own responsibility and reason to change. In our example, we will split the responsibilities into three different classes.

Example Code:
namespace PracticeCode.SOLID
{
    public class OrderCreator
    {
        public void CreateOrder()
        {
            Console.WriteLine("Order created.");
        }
    }

    public class OrderRepository
    {
        public void Save()
        {
            Console.WriteLine("Order saved to DB.");
        }
    }

    public class EmailService
    {
        public void Send()
        {
            Console.WriteLine("Email sent to customer.");
        }
    }

    public class OrderService
    {
        private readonly OrderCreator _orderCreator;
        private readonly OrderRepository _orderRepository;
        private readonly EmailService _emailService;

        public OrderService()
        {
            _orderCreator = new OrderCreator();
            _orderRepository = new OrderRepository();
            _emailService = new EmailService();
        }

        public void ProcessOrder()
        {
            _orderCreator.CreateOrder();
            _orderRepository.Save();
            _emailService.Send();
        }
    }
}
Program.cs
OrderService processor = new OrderService();
processor.ProcessOrder();
Console.ReadLine();
Output:
Order created.
Order saved to DB.
Email sent to customer.

Now each class has only one reason to change. This is the correct example of SRP.

2. O - Open/Closed Principle (OCP)

A class should open for extension but close for modification. It means that you should be able to add new functionality without changing the existing code. This avoids breaking old logic when adding new features.

Bad Example: Multiple types of discount calculations are implemented in a single class. If you want to add a new discount type, you need to modify the same class repeatedly. This violates OCP because modification is required for every new discount.
public class DiscountService
{
    public decimal GetDiscount(string customerType, decimal amount)
    {
        if (customerType == "Regular")
            return amount * 0.05m;

        if (customerType == "Premium")
            return amount * 0.10m;

        return 0;
    }
}

Good Example: We can refactor the above code using Inheritance and Polymorphism. Let's see how we can use the open/close principle to write better code.

Step 1: Create a base class, also called an extension point.
public abstract class Discount
{
    public abstract decimal Calculate(decimal amount);
}

Step 2: Create separate classes for each discount type.
public class RegularDiscount : Discount
{
    public override decimal Calculate(decimal amount)
        => amount * 0.05m;
}

public class PremiumDiscount : Discount
{
    public override decimal Calculate(decimal amount)
        => amount * 0.10m;
}
You can add a new type like:
public class GoldDiscount : Discount
{
    public override decimal Calculate(decimal amount)
        => amount * 0.15m;
}

Step 3: Create a Factory Class to choose the correct discount type.
//----------------Discount Factory----------------//
public static class DiscountFactory
{
    public static Discount GetDiscount(string discountType)
    {
        return discountType.ToLower() switch
        {
            "regular" => new RegularDiscount(),
            "seasonal" => new SeasonalDiscount(),
            _ => throw new ArgumentException("Invalid discount type"),
        };
    }
}

public class Customer
{
    private readonly Discount _discount;

    public Customer(Discount discount)
    {
        _discount = discount;
    }

    public decimal GetFinalAmount(decimal amount)
    {
        return _discount.ApplyDiscount(amount);
    }
}

Step 4: Calling the Factory Method in the Program.cs
class Program
{
    static void Main(string[] args)
    {
        Console.Write("Enter discount type (regular / seasonal): ");
        string type = Console.ReadLine();

        Discount discount = DiscountFactory.CreateDiscount(type);

        Customer customer = new Customer(discount);

        decimal amount = 1000;
        decimal finalAmount = customer.GetFinalAmount(amount);

        Console.WriteLine($"Final amount for {type} discount: {finalAmount}");
    }
}
Output:
Enter discount type (regular / seasonal): seasonal
Final amount for seasonal discount: 800

Now our old code will never change, even if we want to implement any new discount type in the future. This is what the open/closed principle wants.

3. L - Liskov Substitution Principle (LSP)

A child class should be replaceable for its parent class without breaking the program. It means wherever you use a base class, you should be able to use its derived class without causing errors or unexpected behavior. If substituting a child class changes the behavior, it violates LSP.

Bad Example: If the Payment class has Refund(), a CashPayment class that throws an exception for Refund violates LSP.

Example Code: We have a base class with Pay and Refund options that can be overridden by derived classes.
public class Payment
{
    public virtual void Pay(decimal amount)
    {
        Console.WriteLine($"Payment of {amount} done.");
    }

    public virtual void Refund(decimal amount)
    {
        Console.WriteLine($"Refund of {amount} processed.");
    }
}

Now, when we create the CashPayment and CardPayment derived classes, we inherit from the Payment class. CardPayment works fine because it has both Pay and Refund options, but CashPayment does not have a refund option.
public class CashPayment : Payment
{
    public override void Refund(decimal amount)
    {
        throw new NotSupportedException("Cash cannot be refunded!");
    }
}

Here, it will throw a runtime error when you create a child object and assign it to the Parent, because the child class is not substitutable for the Parent class.
Payment payment = new CashPayment();
payment.Refund(100);   // 💥 Crash at runtime!

CashPayment violates LSP because it cannot safely replace Payment’s refund behavior, causing the program to break when substituted.

Good Example: We FIX this by splitting responsibilities using interfaces.
We are splitting the Payment system into two interfaces, IPayment and IRfundable, and different methods inherit and implement them based on their functionality.
namespace PracticeCode.SOLID
{
    public interface IPayment
    {
        void Pay(decimal amount);
    }
    public interface IRfundable
    {
        void Refund(decimal amount);
    }

    public class CardPayment: IPayment, IRfundable
    {
        public void Pay(decimal amount)
            => Console.WriteLine($"Card Amount Paid: {amount}");

        public void Refund(decimal amount)
            => Console.WriteLine($"Card Amount Refund: {amount}");
    }

    public class CashPayment: IPayment
    {
        public void Pay(decimal amount)
            => Console.WriteLine($"Cash Amount Paid: {amount}");
    }

    public class PaymentProcess
    {
        public void ProcessPayment(IPayment payment, decimal amount)
        {
            payment.Pay(amount);
        }
        public void RefundPayment(IRfundable refund, decimal amount)
        {
            refund.Refund(amount);
        }
    }
}

We will create a PaymentProcess object and call the methods defined inside with a child class object. As there is no Refund for Cash payment, we will get a compile-time error when we try to call the refund method (Program.cs).
//LSP
var processPayment = new PaymentProcess();
processPayment.ProcessPayment(new CardPayment(), 100);
processPayment.ProcessPayment(new CashPayment(), 100);
processPayment.RefundPayment(new CardPayment(), 100);
//processPayment.RefundPayment(new CashPayment(), 100); //Compile time error

This is how we can easily replace a child class object with a parent class without breaking the application by following LSP.

4. I - Interface Segregation Principle (ISP)

A class should not be forced to implement methods it does not use. Instead of one large interface, create many smaller interfaces. This keeps code clean, avoids empty methods, and removes unnecessary dependencies.

Bad Example: Having a big IWorker interface with Work() and Eat() forces robots to implement Eat(), which they don’t need.
public interface IWorker
{
    void Work();
    void Eat();
}

public class Robot : IWorker
{
    public void Work() 
    {
        Console.WriteLine("Robot working.");
    }

    public void Eat() 
    {
        throw new NotImplementedException("Robots don't eat!");
    }
}

Robots do NOT eat, but are forced to implement Eat(). This violates ISP because the interface forces unnecessary methods.

Good Example: Instead, splitting interfaces into IWorkable and IFeedable keeps everything clean and avoids unnecessary code.
public interface IWorkable
{
    void Work();
}

public interface IFeedable
{
    void Eat();
}

// Now classes implement only what they need
public class Human : IWorkable, IFeedable
{
    public void Work() => Console.WriteLine("Human working.");
    public void Eat()  => Console.WriteLine("Human eating.");
}

public class Robot : IWorkable
{
    public void Work() => Console.WriteLine("Robot working.");
}

Now use them in the Program.cs
namespace PracticeCode.SOLID
{
    class Program
    {
        static void Main(string[] args)
        {
            // Using IWorkable interface
            IWorkable humanWorker = new Human();
            IWorkable robotWorker = new Robot();

            humanWorker.Work();   // Output: Human working.
            robotWorker.Work();   // Output: Robot working.

            Console.WriteLine();

            // Using IFeedable interface
            IFeedable humanEater = new Human();
            humanEater.Eat();     // Output: Human eating.

            // Robot does NOT implement IFeedable
            // So you cannot do this:
            // IFeedable robotEater = new Robot();  // ❌ Compile-time error (Good!)

            Console.ReadLine();
        }
    }
}

Human implements both interfaces; Robot only implements IWorkable.

5. D - Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions. This makes your code flexible, testable, maintainable, and loosely coupled.

Bad Example: NotificationService depends directly on EmailService

public class EmailService
{
    public void Send(string message)
    {
        Console.WriteLine("EMAIL: " + message);
    }
}

public class NotificationService
{
    private readonly EmailService _emailService = new EmailService(); // ❌ Hard-coded dependency

    public void Notify(string message)
    {
        _emailService.Send(message);
    }
}

Because NotificationService directly depends on EmailService, it becomes tightly coupled, hard to extend, hard to test, and breaks when EmailService changes, clearly violating DIP.

Good Example: Instead of NotificationService calling EmailService directly,
We depend on IMessageService so we can switch between Email, SMS, or WhatsApp without modifying NotificationService.

Step 1: Create an abstraction (Interface) and implement Email & SMS services.
public interface IMessageService
{
    void Send(string message);
}

public class EmailService : IMessageService
{
    public void Send(string message)
    {
        Console.WriteLine("EMAIL: " + message);
    }
}

public class SmsService : IMessageService
{
    public void Send(string message)
    {
        Console.WriteLine("SMS: " + message);
    }
}

Step 2: High-level class depends on abstraction.
public class NotificationService
{
    private readonly IMessageService _messageService;

    public NotificationService(IMessageService messageService)
    {
        _messageService = messageService;
    }

    public void Notify(string message)
    {
        _messageService.Send(message);
    }
}

Step 3: Use it in the Program.cs
class Program
{
    static void Main(string[] args)
    {
        IMessageService service = new SmsService();     // choose service
        NotificationService notifier = new NotificationService(service);

        notifier.Notify("Order shipped!");               // SMS: Order shipped!

        Console.ReadLine();
    }
}

I hope you are now clear about the real-life use case of SOLID principles. By applying SOLID correctly in your C# projects, you make your architecture:

  • Scalable
  • Maintainable
  • Testable
  • Professional

If you're preparing for interviews, mastering SOLID is one of the biggest advantages you can have.

DON'T MISS

Tech News
© all rights reserved
made with by AlgoLesson