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.

TypeScript ngstyle-hello.component.ts
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.

HTML ngstyle-syntax.html
<!-- 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.

HTML style-binding.html
<!-- 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.

HTML ngstyle-vs-style.html
<!-- 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ötigNgStyle aus @angular/commonkein Import
Unit-Suffixnicht direkt, manuellnativ (.px, .rem)
Diff-StrategieObject-Diff über alle Keyspro Property, sehr granular
Bündelt mehrere Props in einer Quelleja, ein Signal kann allenein, je Property eine Quelle
Lesbarkeit bei 5+ Propsgut, ein Blockwird schnell unübersichtlich
Performanceleichter Overhead durch Object-Diffminimal 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.

TypeScript theme-switcher.component.ts
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)QuelleBeispiel
1Template style="..."style="color: red"
2[style]="..." (Map-Form)[style]="{ color: 'red' }"
3[ngStyle]="{...}"[ngStyle]="{ color: 'red' }"
4[style.x]="..." (einzeln)[style.color]="'red'"
5Direktiven-host Style-Bindinghost: &#123; '[style.color]': 'c' &#125;

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.

TypeScript heatmap-cell.component.ts
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.

TypeScript svg-theme-fallback.component.ts
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.

TypeScript custom-property-pattern.component.ts
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-fill ohne 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

/ Weiter

Zurück zu Directives

Zur Übersicht