NgClass and NgStyle — Dynamic Classes and Styles

📋 Table of Contents
  1. NgClass and NgStyle
  2. Common Mistakes

[ngClass] and [ngStyle] are attribute directives that apply dynamic CSS classes and inline styles to elements. While single-class and single-style bindings ([class.active], [style.color]) are simpler for individual values, [ngClass] and [ngStyle] handle multiple classes and styles simultaneously with object, array, and string syntax. For most production Angular applications, CSS class-based styling via [ngClass] is preferred over inline styles — it keeps styles in the stylesheet, enables theme switching, and is more maintainable.

NgClass and NgStyle

import { NgClass, NgStyle } from '@angular/common';

@Component({
  standalone:  true,
  imports:    [NgClass, NgStyle],
  template: `
    <!-- ── [ngClass] — object syntax (most common) ───────────────────────── -->
    <span [ngClass]="{
      'badge':       true,
      'badge-draft':      post.status === 'draft',
      'badge-review':     post.status === 'review',
      'badge-published':  post.status === 'published',
      'badge-archived':   post.status === 'archived',
      'badge-featured':   post.isFeatured
    }">{{ post.status }}</span>

    <!-- ── [ngClass] — string syntax ────────────────────────────────────── -->
    <div [ngClass]="'card ' + (post.isPublished ? 'published' : 'draft')"></div>

    <!-- ── [ngClass] — array syntax ──────────────────────────────────────── -->
    <div [ngClass]="['base-class', conditionalClass, roleClass]"></div>

    <!-- ── [ngClass] — computed from method ──────────────────────────────── -->
    <article [ngClass]="getPostClasses(post)"></article>

    <!-- ── [class.name] — single class toggle (cleaner for one class) ──────── -->
    <button [class.active]="isSelected"
            [class.disabled]="isLoading()">Submit</button>

    <!-- ── [ngStyle] — multiple inline styles (use sparingly) ────────────── -->
    <div [ngStyle]="{
      'opacity':     post.isPublished ? 1 : 0.5,
      'font-size':   fontSize + 'px',
      'border-left': '4px solid ' + categoryColor()
    }"></div>

    <!-- ── [style.property] — single style (cleaner for one style) ─────────── -->
    <div [style.color]="isDark ? '#fff' : '#000'"
         [style.font-size.rem]="1.25"></div>
  `,
})
export class PostStatusComponent {
  @Input({ required: true }) post!: PostDto;
  isSelected    = false;
  isLoading     = signal(false);
  isDark        = false;
  fontSize      = 16;
  categoryColor = signal('#2196f3');

  get conditionalClass(): string { return this.post.isPublished ? 'live' : 'staging'; }
  get roleClass():        string { return 'role-' + this.post.authorRole; }

  getPostClasses(post: PostDto): Record<string, boolean> {
    return {
      'post-card':      true,
      'post-featured':  post.isFeatured,
      'post-published': post.isPublished,
      'post-long':      post.wordCount > 2000,
    };
  }
}
Note: When using [ngClass] with the object syntax, existing static CSS classes on the element are preserved — [ngClass] adds and removes additional classes on top of them. You can combine static classes with dynamic ones: <div class="card" [ngClass]="{'card-large': isLarge}">. The static class="card" is always present; card-large is added or removed dynamically. This is different from [class]="expression" (without a dot) which replaces the entire class attribute with the expression value.
Tip: For complex class logic, move it to a computed property or getter in the component class rather than embedding complex expressions in the template. [ngClass]="getStatusClasses()" with the logic in a component method is more readable, testable, and maintainable than a complex inline object with multiple ternary operators. With Signals: statusClasses = computed(() => ({ 'published': this.post()?.isPublished, ... })) — the classes automatically update when the signal changes.
Warning: Prefer CSS class-based styling with [ngClass] over inline styles with [ngStyle]. Inline styles have the highest CSS specificity, making them hard to override from stylesheets, and they do not benefit from browser caching (class-based styles are in the cached stylesheet). Reserve [ngStyle] for truly dynamic values that cannot be expressed as CSS classes — like user-defined colour pickers, dynamic dimensions from calculated values, or animation frame values. Even then, consider CSS custom properties (--primary-color: var) as an alternative.

Common Mistakes

Mistake 1 — Using [class]=”expression” instead of [ngClass] (replaces all classes)

❌ Wrong — [class]="isActive ? 'active' : ''" — removes all other classes, leaving only the bound value.

✅ Correct — use [class.active]="isActive" or [ngClass]="{'active': isActive}" to toggle without removing others.

Mistake 2 — Overusing [ngStyle] for styling (maintenance nightmare)

❌ Wrong — [ngStyle]="{'background': '#fff', 'padding': '16px', 'border-radius': '8px'}" in every component.

✅ Correct — define CSS classes in the component’s stylesheet; use [ngClass] to apply them conditionally.

🧠 Test Yourself

An element has class="card" and [ngClass]="{'card-featured': post.isFeatured}". When isFeatured is true, what are the element’s CSS classes?