For dynamic classes and styles use the native bindings [class] / [class.x] and [style] / [style.x]. The ngClass and ngStyle directives still exist, but the current style guide recommends the native bindings, and v21 added automatic migrations for them.
Classes
<!-- A single conditional class -->
<li [class.active]="isActive()">...</li>
<!-- Several classes: string, array or object -->
<div [class]="'card elevated'"></div>
<div [class]="['card', size()]"></div>
<div [class]="{ card: true, selected: isSelected(), disabled: !enabled() }"></div>
<!-- Static classes and those from the binding are COMBINED, not overwritten -->
<button class="btn" [class.btn-primary]="primary()">OK</button>Styles
<!-- A CSS property, with a unit -->
<div [style.width.px]="width()"></div>
<div [style.height.%]="progress()"></div>
<div [style.font-size.rem]="scale()"></div>
<!-- CSS variables (custom properties) -->
<div [style.--accent]="brandColor()"></div>
<!-- Several styles at once: string or object -->
<div [style]="{ 'background-color': bg(), opacity: dim() ? 0.5 : 1 }"></div>Why not ngClass / ngStyle
<!-- ✓ New: native binding, no import, faster -->
<div [class]="{ active: isActive(), 'is-big': size() === 'lg' }"></div>
<div [style.width.px]="width()"></div><!-- ✗ Old: needs the NgClass / NgStyle import (or CommonModule) -->
<div [ngClass]="{ active: isActive, 'is-big': size === 'lg' }"></div>
<div [ngStyle]="{ 'width.px': width }"></div>- No import: it is part of the compiler, not a directive.
- Faster: there is no differ comparing the object on every change detection.
- Easier to read for the most common case — a conditional class:
[class.active]="x()".
# v21: automatic migrations
ng generate @angular/core:ngclass-to-class
ng generate @angular/core:ngstyle-to-styleClasses on the host element: host
For a component’s host element (the <app-button> tag itself) use the host property of the metadata, not @HostBinding. From v21, these bindings are type-checked too.
// Classes on the component’s host element → the `host` metadata, not @HostBinding
@Component({
selector: 'app-button',
host: {
class: 'ui-btn', // static
'[class.is-loading]': 'loading()', // dynamic
'[class]': 'variant()', // 'primary' | 'ghost'
'[style.--hue]': 'hue()',
'[attr.aria-busy]': 'loading()',
},
template: `<ng-content />`,
})
export class Button {
readonly variant = input<'primary' | 'ghost'>('primary');
readonly loading = input(false, { transform: booleanAttribute });
readonly hue = input(330);
}Who wins when several places set the same style
The same element can get a style from five places: the component's own host, a directive's host, a static style attribute, a [style] map and a [style.x] binding. Angular doesn't let the last one written win — it applies a fixed order of priority.
- overridden
- overridden
- overridden
The winner is read from the browser (getComputedStyle), not computed by the lab.
From the top: a single-property binding beats a map, a map beats a static value — and anything in the template beats what directives and the component set on their host. Notice that even a static style attribute in the template wins over a directive's host binding. The same order applies to classes: [class.active]="false" removes an active written in a static class attribute. One more detail: a binding that is “off” should be undefined, which hands the decision to the layer below. null means “remove this style”: switch to it and the background disappears, even with every other layer on.
// from the highest priority to the lowest:
// 1. [style.background]="…" a property binding in the template
// 2. [style]="{ background: … }" a map binding in the template
// 3. style="background: …" a static value in the template
// 4. host bindings of a directive on the element
// 5. host bindings of the component itself
@Component({
template: `
<app-swatch
appTint
style="background: yellow"
[style]="{ background: 'green' }"
[style.background]="'red'"
/>
`,
})
export class Page {} // → red: number 1 is on
@Directive({ selector: '[appTint]', host: { '[style.background]': "'blue'" } })
export class Tint {}
@Component({ selector: 'app-swatch', host: { '[style.background]': "'gray'" }, template: '' })
export class Swatch {}Common pitfalls
[class]and[class.x]on the same element work together; the priority order: the specific binding ([class.x]) wins.- Mutating the classes object: if you change the same object in place, Angular may not notice. Create a new one (or use a
computed()). - Forgotten units:
[style.width]="200"does nothing; write[style.width.px]="200". - Encapsulated styles: classes applied to projected children are not styled by your component’s CSS (use
:hostor CSS variables). How encapsulation works is shown live on Component styles.