[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,
};
}
}
[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.[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.[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.