NgStyle ist das Inline-Style-Pendant zu NgClass: Eine Direktive aus @angular/common, die mehrere CSS-Properties auf einmal aus einer Object-Map auf das Element schreibt — [ngStyle]="{ color: 'red', fontSize: '14px' }". Anders als das native [style.x]="..."-Binding, das immer eine einzelne Property setzt, frisst ngStyle ein ganzes Objekt und merged es bei jedem Change-Detection-Zyklus auf das Element. Genau das macht sie nützlich, wenn 3+ Style-Werte aus derselben Quelle (Signal, computed, Service) zusammen reisen — und genau das macht sie verzichtbar, sobald CSS-Variablen oder einzelne [style.x]-Bindings reichen.
Dieser Artikel zeigt mit Angular 21, wie die Object-Map intern aussieht, warum CamelCase und kebab-case beide funktionieren, wie Unit-Suffixe wie [style.padding.px]="num" dieselbe Aufgabe oft eleganter lösen, in welcher Reihenfolge ngStyle, [style.x] und das statische style="..."-Attribut kollidieren und wann eine CSS-Custom-Property die bessere Wahl ist. Eine vollständige Heatmap-Kachel und ein Theme-Switcher mit Signals zeigen das Ganze in echten Komponenten.
Direktive für mehrere Inline-Styles auf einmal
NgStyle ist eine Attribute Directive aus dem @angular/common-Paket. Sie nimmt ein Objekt entgegen, dessen Schlüssel CSS-Property-Namen sind und dessen Werte die zugehörigen Style-Werte. Beim Change-Detection-Lauf vergleicht Angular das Objekt mit dem zuletzt gesetzten Stand und schreibt geänderte Properties auf das style-Attribut des Host-Elements.
import { Component, signal } from '@angular/core';
import { NgStyle } from '@angular/common';
@Component({
selector: 'app-badge',
standalone: true,
imports: [NgStyle],
template: `
<span [ngStyle]="{
color: textColor(),
backgroundColor: bgColor(),
padding: '4px 8px',
borderRadius: '4px'
}">
Status
</span>
`,
})
export class BadgeComponent {
textColor = signal('#fff');
bgColor = signal('#0a7');
}Die Direktive ist konzeptionell das Pendant zu NgClass — nur dass NgClass Klassen toggelt und NgStyle Style-Properties direkt setzt. Beide existieren seit den frühen Angular-Versionen, beide werden heute in vielen Fällen vom nativen Property-Binding ([class.x], [style.x]) abgelöst.
Object-Map mit CamelCase oder kebab-case
NgStyle akzeptiert die CSS-Property-Namen sowohl in CamelCase (fontSize) als auch in kebab-case ('font-size'). Angular normalisiert intern auf kebab-case, weil das style-Attribut im DOM in dieser Form gesetzt wird. CamelCase ist ohne Anführungszeichen schreibbar, kebab-case braucht Quotes wegen des Bindestrichs.
<!-- CamelCase, Properties ohne Quotes -->
<div [ngStyle]="{
fontSize: '14px',
backgroundColor: 'tomato',
paddingLeft: '12px'
}">A</div>
<!-- kebab-case, Property-Keys mit Quotes -->
<div [ngStyle]="{
'font-size': '14px',
'background-color': 'tomato',
'padding-left': '12px'
}">B</div>
<!-- Mischung ist erlaubt, aber unschön -->
<div [ngStyle]="{
fontSize: '14px',
'background-color': 'tomato'
}">C</div>Die Werte sind immer Strings im CSS-Format — '14px', nicht 14. Für numerische Werte ohne Einheit (z. B. lineHeight: '1.4', opacity: '0.8', zIndex: '10') reicht der String. Sollen Zahlen ohne Quotes kommen, hilft das Unit-Suffix-Pattern aus dem nächsten Abschnitt — das ist allerdings nur am [style.x]-Binding direkt verfügbar, nicht innerhalb der ngStyle-Map.
Eine einzelne Property — kürzer und ohne Import
Für eine einzelne Style-Property ist [style.x]="value" immer die kürzere und schnellere Wahl. Es benötigt keinen Import, kein Object-Diff und keine Direktive — Angular setzt die Property direkt aus dem Compiler heraus.
<!-- Eine Property, einfacher Wert -->
<div [style.color]="textColor()">Text</div>
<!-- Mit Unit-Suffix: Wert wird automatisch px-suffixed -->
<div [style.padding.px]="paddingValue()">Padding</div>
<div [style.font-size.rem]="size()">Font</div>
<div [style.width.%]="progress()">Progress</div>
<!-- Komplexer Wert (transform) — Suffix funktioniert hier nicht -->
<div [style.transform]="'translateX(' + offset() + 'px)'">Move</div>Das Unit-Suffix (.px, .rem, .%, .em, .deg, .pt) ist ein Killer-Feature: Du gibst eine reine Zahl in den Component-State, und Angular suffixed beim Setzen automatisch die Einheit. Damit fällt das umständliche String-Concat ('padding: ' + n + 'px') komplett weg.
ngStyle vs. mehrere [style.x]-Bindings
Wenn 3+ Style-Properties dynamisch zusammen reisen, stellt sich die Frage: ein [ngStyle]="{...}" oder mehrere [style.x]-Bindings nebeneinander? Beide funktionieren, beide sind geläufig — der Unterschied liegt in Lesbarkeit, Performance und Update-Mechanik.
<!-- Variante A: ngStyle mit Object-Map -->
<div [ngStyle]="{
color: textColor(),
backgroundColor: bgColor(),
padding: padding() + 'px',
borderRadius: radius() + 'px'
}">Card A</div>
<!-- Variante B: vier einzelne [style.x]-Bindings -->
<div
[style.color]="textColor()"
[style.background-color]="bgColor()"
[style.padding.px]="padding()"
[style.border-radius.px]="radius()"
>Card B</div>| Aspekt | [ngStyle]="{...}" | mehrere [style.x] |
|---|---|---|
| Import nötig | NgStyle aus @angular/common | kein Import |
| Unit-Suffix | nicht direkt, manuell | nativ (.px, .rem) |
| Diff-Strategie | Object-Diff über alle Keys | pro Property, sehr granular |
| Bündelt mehrere Props in einer Quelle | ja, ein Signal kann alle | nein, je Property eine Quelle |
| Lesbarkeit bei 5+ Props | gut, ein Block | wird schnell unübersichtlich |
| Performance | leichter Overhead durch Object-Diff | minimal schneller |
Faustregel: Bei 1–2 Properties immer [style.x]. Ab 3+ Properties, die aus derselben Quelle (z. B. einem computed-Signal) kommen, kann ngStyle lesbarer sein — aber nur, wenn du den Import in Kauf nehmen willst.
ngStyle + computed Signal = reaktiver Style-Block
Da ngStyle ein beliebiges Objekt akzeptiert, lässt sich der gesamte Style-Block in ein computed-Signal auslagern. Das hält das Template schlank und macht die Style-Logik testbar.
import { Component, signal, computed } from '@angular/core';
import { NgStyle } from '@angular/common';
type Theme = 'light' | 'dark' | 'sepia';
@Component({
selector: 'app-theme-card',
standalone: true,
imports: [NgStyle],
template: `
<div [ngStyle]="cardStyle()">
<h2>Aktives Theme: {{ theme() }}</h2>
<button (click)="cycle()">Wechseln</button>
</div>
`,
})
export class ThemeCardComponent {
theme = signal<Theme>('light');
cardStyle = computed(() => {
switch (this.theme()) {
case 'dark':
return { backgroundColor: '#1a1a1a', color: '#eee', borderColor: '#333' };
case 'sepia':
return { backgroundColor: '#f4ecd8', color: '#5b4636', borderColor: '#c4b89c' };
default:
return { backgroundColor: '#fff', color: '#222', borderColor: '#ddd' };
}
});
cycle() {
const order: Theme[] = ['light', 'dark', 'sepia'];
const next = order[(order.indexOf(this.theme()) + 1) % order.length];
this.theme.set(next);
}
}Der computed()-Wrapper sorgt dafür, dass der Style-Block nur dann neu berechnet wird, wenn sich theme() tatsächlich ändert — und nicht bei jedem Change-Detection-Lauf. Das ist gerade unter OnPush und in Zoneless-Apps nahezu kostenlos.
Wer gewinnt: ngStyle, [style.x] oder das style-Attribut?
Auf einem Element können gleichzeitig mehrere Quellen dieselbe CSS-Property setzen: das statische style="..."-Attribut, ein [ngStyle]="{...}"-Binding, ein direktes [style.x]="..."-Binding und sogar host-Bindings einer Direktive. Angular hat dafür eine feste Reihenfolge.
| Priorität (niedrig → hoch) | Quelle | Beispiel |
|---|---|---|
| 1 | Template style="..." | style="color: red" |
| 2 | [style]="..." (Map-Form) | [style]="{ color: 'red' }" |
| 3 | [ngStyle]="{...}" | [ngStyle]="{ color: 'red' }" |
| 4 | [style.x]="..." (einzeln) | [style.color]="'red'" |
| 5 | Direktiven-host Style-Binding | host: { '[style.color]': 'c' } |
In der Praxis heißt das: Ein [style.color] an Element B überschreibt einen color-Eintrag aus [ngStyle] am selben Element. Eine host-Bindung einer angewendeten Direktive überschreibt wiederum beides. Das ist hilfreich, kann aber für Aha-Momente sorgen, wenn ein ngStyle-Update „nicht durchkommt“ — meistens ist eine Direktive mit host-Style daran schuld.
Eine Kachel, die Werte als Farb- und Padding-Intensität rendert
Eine Heatmap-Kachel ist der Klassiker für ngStyle: Aus einem numerischen Wert leiten sich gleich mehrere Style-Properties ab — Hintergrundfarbe (HSL), Textfarbe (Kontrast), Padding (visuelles Gewicht), Border (Rand-Highlight). Genau hier zahlt sich das Object-Pattern aus.
import { Component, input, computed } from '@angular/core';
import { NgStyle } from '@angular/common';
@Component({
selector: 'app-heatmap-cell',
standalone: true,
imports: [NgStyle],
template: `
<div class="cell" [ngStyle]="cellStyle()">
{{ value() }}
</div>
`,
styles: [`
.cell {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 48px;
border-radius: 4px;
font-variant-numeric: tabular-nums;
transition: background-color 200ms, padding 200ms;
}
`],
})
export class HeatmapCellComponent {
// value zwischen 0 und 1
value = input.required<number>();
cellStyle = computed(() => {
const v = Math.max(0, Math.min(1, this.value()));
const hue = 220 - v * 220; // 220 (blau) -> 0 (rot)
const lightness = 90 - v * 50; // 90% -> 40%
const padding = 6 + v * 14; // 6px -> 20px
return {
backgroundColor: `hsl(${hue}, 80%, ${lightness}%)`,
color: lightness > 60 ? '#222' : '#fff',
padding: `${padding}px`,
fontWeight: v > 0.7 ? '700' : '400',
};
});
}Hier kommen vier Style-Properties aus derselben Berechnungsquelle — der Heatmap-Wert. Mit vier [style.x]-Bindings müsstest du dieselbe Computed-Logik viermal getrennt aufbauen oder in vier computed-Signals verteilen. Ein einziges cellStyle = computed(...) ist deutlich kürzer.
Wenn Custom Properties keine Option sind
Moderne Apps lösen Theming am elegantesten über CSS-Custom-Properties (--theme-bg: ...) plus :root[data-theme="dark"]-Selektoren. Es gibt aber Edge-Cases, in denen das nicht geht: ältere SVG-Bibliotheken, die keine CSS-Variablen erkennen; programmatisch ge-renderter Canvas-Output; oder Components in einem Shadow-DOM, das keine Variable von außen erbt.
In diesen Szenarien ist ngStyle ein legitimer Fallback — die Werte landen direkt am Element, statt durch eine Variable durchzureichen.
import { Component, signal, computed } from '@angular/core';
import { NgStyle } from '@angular/common';
@Component({
selector: 'app-svg-icon',
standalone: true,
imports: [NgStyle],
template: `
<svg viewBox="0 0 24 24" width="24" height="24">
<!-- Manche SVG-Libs respektieren CSS-Variables nicht;
dann setzt ngStyle 'fill' direkt aufs Element. -->
<circle cx="12" cy="12" r="10"
[ngStyle]="iconStyle()" />
</svg>
`,
})
export class SvgIconComponent {
isDark = signal(false);
iconStyle = computed(() => ({
fill: this.isDark() ? '#eee' : '#222',
stroke: this.isDark() ? '#888' : '#444',
strokeWidth: '1.5',
}));
}CSS-Variablen schlagen ngStyle in fast allen Fällen
In modernen Browser-Umgebungen ist die CSS-Custom-Property der bessere Default für jedes Theming-, Branding- oder Variable-Style-Szenario. Eine Variable lebt im Stylesheet, kann von Cascade und Specificity profitieren, ist via prefers-color-scheme mediafähig und kostet praktisch keinen JS-Overhead. ngStyle setzt dagegen bei jedem Change-Detection-Lauf inline-Styles direkt auf das Element.
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-themed',
standalone: true,
template: `
<!-- Nur EINE Variable wird per ngStyle gesetzt;
die ganze Style-Hierarchie nutzt sie via var(). -->
<div class="card"
[style.--accent]="accent()">
<h3>Branded Card</h3>
<button class="btn">Action</button>
</div>
`,
styles: [`
.card {
border: 2px solid var(--accent, steelblue);
padding: 16px;
}
.card h3 { color: var(--accent); }
.btn {
background: var(--accent);
color: #fff;
border: 0;
padding: 6px 12px;
}
`],
})
export class ThemedComponent {
accent = signal('#0a7');
}[style.--accent] setzt die Custom Property einmal aufs Element — die Cascade verteilt sie auf Kind-Elemente, Pseudo-Elements, Hover-States. Das ist deutlich mächtiger und billiger als 8 verschiedene [ngStyle]-Properties.
Wann ngStyle trotzdem sinnvoll bleibt:
- Werte, die wirklich nur zur Laufzeit aus JS kommen (Heatmap, Chart-Position, Drag-Offset).
- Properties, die nicht in Style-Sheets stehen können (z. B. SVG-
fillohne CSS-Var-Support). - Schnelle Prototypen oder Demos, wo das Stylesheet-Setup zu viel Overhead wäre.
Häufige Fragen zu NgStyle
Wann ngStyle vs. [style.x]?
[style.x] ist die Default-Wahl: kein Import, native Unit-Suffixe, marginal schneller. ngStyle lohnt sich erst, wenn 3+ Properties dynamisch zusammen aus derselben Quelle kommen — typisch bei einem computed-Signal, das den ganzen Style-Block ableitet. Bei 1–2 Properties immer [style.x].
Warum funktionieren CamelCase und kebab-case beide?
Angular normalisiert die Object-Keys intern auf kebab-case (das DOM-Format). fontSize und ‘font-size’ landen am Ende identisch im style-Attribut. Bleib in einem Projekt konsistent — meist gewinnt CamelCase, weil keine Quotes nötig sind.
Performance-Unterschied zu [style.x]?
Marginal. NgStyle implementiert intern einen Object-Diff über alle Keys; das native [style.x]-Binding vergleicht nur einen Wert. In Hot-Loops (Animation pro Frame, Drag-Updates) misst das zusammen, ansonsten ist es Rauschen. Wenn du Performance willst: [style.x] + Unit-Suffix.
Funktioniert ngStyle mit @keyframes oder transitions?
Ja — ngStyle setzt die Werte am style-Attribut, und CSS-Transitions reagieren darauf normal. Wenn du transition: background 200ms in der Klasse hast, transitioniert ein backgroundColor-Wechsel via ngStyle sauber. Animationen mit @keyframes laufen unabhängig davon — die feuern nur, wenn die animation-Property selbst gesetzt wird.
Wie kombiniere ich ngStyle mit !important?
Direkt im String-Wert: [ngStyle]=”{ color: ‘red !important’ }”. Angular setzt den Wert 1:1 — der Browser parst !important in der gewohnten Form. Das ist allerdings selten nötig: Inline-Styles haben in der CSS-Cascade ohnehin Vorrang vor allen externen Klassen.
ngStyle in Standalone-Components?
imports: [NgStyle] reicht — das gesamte CommonModule wäre Overkill. Für klassische NgModule-Apps wird NgStyle über CommonModule ausgeliefert. Seit v15 ist NgStyle standalone und tree-shakable, du zahlst keinen Bundle-Tax für Direktiven, die du nicht nutzt.
Sicher gegen XSS?
Angular sanitisiert Style-Werte: url(javascript:…)-Versuche werden gefiltert, ebenso wie offensichtliche Skript-Injektionen. Du solltest trotzdem keinen ungeprüften User-Input direkt in Style-Werte schreiben — semantisch geprüfte Whitelists (z. B. nur Hex-Farben) sind sicherer als Vertrauen auf den Sanitizer allein.
Weiterführende Ressourcen
Externe Quellen
- NgStyle API – Angular.dev
- Class and Style Bindings – Angular.dev
- Attribute Directives Guide – Angular.dev