Zahlen sind selten nur Zahlen — sie sind Geldbeträge, Quoten, Statistiken oder Messwerte. Damit die Ausgabe in jedem Land richtig aussieht, liefert Angular drei eng verwandte Formatierungs-Pipes: number (DecimalPipe), percent (PercentPipe) und currency (CurrencyPipe). Alle drei teilen das gleiche digitsInfo-Format und arbeiten Locale-aware. Dieser Artikel widmet sich den ersten beiden ausführlich und ordnet sie sauber neben der CurrencyPipe ein.
Drei Formatierungs-Pipes im Überblick
Angular kennt für reine Zahlen-Ausgaben drei Standard-Pipes aus @angular/common:
DecimalPipe— verwendet im Template als| number— formatiert allgemeine Zahlen mit Tausender-Trenner und Nachkommastellen.PercentPipe— verwendet als| percent— multipliziert intern mit 100 und hängt das%-Zeichen an.CurrencyPipe— verwendet als| currency— fügt ein Währungssymbol hinzu und nutzt ISO-4217-Defaults für Nachkommastellen.
Alle drei Pipes leben im gleichen Modul, sind als Standalone-Pipes einzeln importierbar, sind pure (Caching bei Reference-Equality) und respektieren die globale LOCALE_ID. Der gemeinsame Format-String digitsInfo macht sie zu einer Familie — wenn du eine kannst, kannst du alle.
DecimalPipe — Grundsyntax
Die DecimalPipe formatiert eine Zahl gemäß der aktiven Locale. Im einfachsten Fall reicht der Pipe-Aufruf ohne Argumente:
<!-- Default-Locale en-US -->
<p>{{ 3.14159 | number }}</p>
<!-- Ausgabe: 3.142 (gerundet auf 3 Nachkommastellen) -->
<!-- Mit digitsInfo: exakt 2 Nachkommastellen -->
<p>{{ 3.14159 | number:'1.2-2' }}</p>
<!-- Ausgabe: 3.14 -->
<!-- Mit Tausender-Trenner -->
<p>{{ 1234567.89 | number:'1.2-2' }}</p>
<!-- Ausgabe: 1,234,567.89 (en-US) bzw. 1.234.567,89 (de-DE) -->Die vollständige Template-Syntax lautet:
{{ wert | number : digitsInfo : locale }}
Der erste optionale Parameter ist digitsInfo, der zweite die Locale-ID (z. B. 'en-US', 'de-DE', 'fr'). Beide Parameter sind unabhängig voneinander setzbar — wer nur die Locale ändern will, gibt trotzdem ein digitsInfo an oder nutzt einen leeren String.
digitsInfo-Format im Detail
Das Format des digitsInfo-Strings ist:
{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}
minIntegerDigits— Mindestanzahl der Vorkommastellen. Standardwert:1. Wird mit führenden Nullen aufgefüllt.minFractionDigits— Mindestanzahl der Nachkommastellen. Standardwert:0. Wird mit nachgestellten Nullen aufgefüllt.maxFractionDigits— Maximale Nachkommastellen. Standardwert:3fürDecimalPipe,2fürCurrencyPipe. Beim Überschreiten wird zur nächsten Stelle gerundet.
digitsInfo | Eingabe 1234.5 | Ausgabe (en-US) | Bedeutung |
|---|---|---|---|
'1.0-0' | 1234.5 | 1,235 | auf ganze Zahl runden |
'1.2-2' | 1234.5 | 1,234.50 | exakt 2 Nachkommastellen, Padding mit Null |
'1.0-2' | 1234.5 | 1,234.5 | bis zu 2 Nachkommastellen, trim trailing zeros |
'3.0-0' | 7 | 007 | mind. 3 Vorkommastellen, Padding mit führenden Nullen |
'1.4-4' | 1234.5 | 1,234.5000 | exakt 4 Nachkommastellen erzwingen |
<!-- Eingabe: 3.14159 -->
<p>{{ 3.14159 | number:'4.1-5' }}</p>
<!-- Ausgabe: 0,003.14159 (4 Vorkomma-Stellen Padding, bis zu 5 Nachkomma) -->
<p>{{ 3.14159 | number:'4.1-5':'fr' }}</p>
<!-- Ausgabe: 0 003,14159 (französische Locale, Leerzeichen + Komma) -->PercentPipe
Die PercentPipe ist ein dünner Wrapper um DecimalPipe: Sie multipliziert den Eingabewert intern mit 100 und hängt das Locale-spezifische Prozentzeichen an.
<!-- 0.5 ist intern bereits "50%" -->
<p>{{ 0.5 | percent }}</p>
<!-- Ausgabe (en-US): 50% -->
<!-- digitsInfo wirkt nach der Multiplikation auf den Endwert -->
<p>{{ 0.12345 | percent:'1.2-2' }}</p>
<!-- Ausgabe: 12.35% -->
<!-- Französische Locale -->
<p>{{ 1.3495 | percent:'4.3-5':'fr' }}</p>
<!-- Ausgabe: 0 134,950 % -->Wichtig: PercentPipe erwartet Bruchzahlen (0.5 für 50 %), nicht bereits skalierte Prozentwerte. Wer aus einer API einen Wert wie 50 für „50 %“ bekommt, muss entweder vorher dividieren oder lieber die DecimalPipe mit einem Suffix nutzen:
<!-- API liefert: rate = 50 (also "50 Prozent") -->
<!-- Falsch: percent multipliziert ein zweites Mal mit 100 -->
<p>{{ 50 | percent }}</p>
<!-- Ausgabe: 5,000% — kaputt -->
<!-- Richtig 1: vorher /100 -->
<p>{{ (50 / 100) | percent:'1.0-0' }}</p>
<!-- Ausgabe: 50% -->
<!-- Richtig 2: DecimalPipe + Suffix -->
<p>{{ 50 | number:'1.0-0' }} %</p>
<!-- Ausgabe: 50 % -->Locale-Verhalten
Beide Pipes lesen die Default-Locale aus dem DI-Token LOCALE_ID. Ohne weitere Konfiguration liefert Angular 'en-US' — also Punkt als Dezimal-Trenner und Komma als Tausender-Trenner. Für deutsche Apps ist genau das umgekehrt.
Damit deutsche Formatierung greift, müssen zwei Dinge geschehen:
- Die Locale-Daten müssen einmal über
registerLocaleDataregistriert werden. - Der Provider
LOCALE_IDmuss auf den gewünschten Wert gesetzt werden.
import { ApplicationConfig, LOCALE_ID } from '@angular/core';
import { registerLocaleData } from '@angular/common';
import localeDe from '@angular/common/locales/de';
// Daten einmal global registrieren
registerLocaleData(localeDe);
export const appConfig: ApplicationConfig = {
providers: [
{ provide: LOCALE_ID, useValue: 'de-DE' }
]
};Anschließend liefert {{ 1234.5 | number:'1.2-2' }} automatisch 1.234,50 — ohne Anpassung im Template. Die PercentPipe nutzt dieselbe LOCALE_ID und gibt 50 % (Leerzeichen vor Prozent) für de-DE aus.
Locale wechseln pro Pipe
Manchmal soll eine einzelne Stelle anders formatiert werden — etwa eine internationale Tabellenzeile in einem deutschsprachigen Dashboard. Beide Pipes akzeptieren als letztes Argument eine Locale-ID:
<!-- Globale Locale ist de-DE, hier explizit en-US -->
<p>{{ 1234.5 | number:'1.2-2':'en-US' }}</p>
<!-- Ausgabe: 1,234.50 -->
<p>{{ 0.5 | percent:'1.0-0':'en-US' }}</p>
<!-- Ausgabe: 50% (kein Leerzeichen vor %) -->
<!-- Französische Anzeige -->
<p>{{ 0.12345 | percent:'1.2-2':'fr' }}</p>
<!-- Ausgabe: 12,35 % -->Voraussetzung: Die jeweilige Locale muss vorher per registerLocaleData registriert sein, sonst wirft Angular einen Laufzeitfehler. Bei dynamisch geladenen Locales lohnt sich Lazy-Loading der Locale-Module.
Praxis: Statistik-Dashboard
Ein typischer Anwendungsfall ist ein KPI-Dashboard, das alle drei Pipes nebeneinander zeigt — Umsatz (Currency), Wachstum (Percent), Stückzahlen (Decimal):
import { Component, signal } from '@angular/core';
import { DecimalPipe, PercentPipe, CurrencyPipe } from '@angular/common';
type Kpi = { label: string; revenue: number; growth: number; units: number };
@Component({
selector: 'app-dashboard',
imports: [DecimalPipe, PercentPipe, CurrencyPipe],
template: `
<table>
<thead>
<tr>
<th>Region</th>
<th>Umsatz</th>
<th>Wachstum</th>
<th>Stück</th>
</tr>
</thead>
<tbody>
@for (row of kpis(); track row.label) {
<tr>
<td>{{ row.label }}</td>
<td>{{ row.revenue | currency:'EUR':'symbol':'1.2-2' }}</td>
<td>{{ row.growth | percent:'1.1-1' }}</td>
<td>{{ row.units | number:'1.0-0' }}</td>
</tr>
}
</tbody>
</table>
`
})
export class DashboardComponent {
kpis = signal<Kpi[]>([
{ label: 'DACH', revenue: 1234567.89, growth: 0.087, units: 12345 },
{ label: 'EMEA', revenue: 987654.32, growth: 0.124, units: 9876 },
{ label: 'APAC', revenue: 456789.10, growth: -0.032, units: 4567 }
]);
}Mit LOCALE_ID = 'de-DE' rendert die erste Tabellenzeile als 1.234.567,89 €, 8,7 %, 12.345. Drei Werte, drei Pipes, ein konsistenter Look.
Vergleich zu Intl.NumberFormat direkt
Hinter den Kulissen nutzt Angular ähnliche Konzepte wie das native Intl.NumberFormat. Wer keinen Pipe-Aufruf möchte (etwa in einer reinen Service-Klasse oder beim Logging), kann direkt die Browser-API nutzen:
// PercentPipe-Äquivalent
const formatted = new Intl.NumberFormat('de-DE', {
style: 'percent',
minimumFractionDigits: 1,
maximumFractionDigits: 1
}).format(0.087);
// Ausgabe: '8,7 %'
// DecimalPipe-Äquivalent ohne Tausender-Trenner
const noGroup = new Intl.NumberFormat('de-DE', {
useGrouping: false,
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(1234567.89);
// Ausgabe: '1234567,89'| Aspekt | Angular Pipe | Intl.NumberFormat |
|---|---|---|
| Verwendung im Template | direkt: | number | nur indirekt über Property |
| Caching | pure pipe, automatisch | manuelles Memoizing nötig |
| Locale-Daten | registerLocaleData Pflicht | im Browser eingebaut |
useGrouping: false | nicht möglich | nativ unterstützt |
BigInt-Support | seit Angular 16 | nativ |
| SSR-Konsistenz | gleich, wenn Locale registriert | gleich, wenn ICU vorhanden |
Faustregel: Im Template fast immer Pipe. In Services und Tests lieber Intl.NumberFormat direkt — weniger DI-Overhead, deutlich weniger Mocking.
Häufige Fragen zu DecimalPipe und PercentPipe
Wann nehme ich DecimalPipe, wann PercentPipe?
Wenn dein Wert bereits in Bruchform vorliegt (0.5 für 50 %), nutze die PercentPipe — sie multipliziert intern mit 100. Liegt der Wert schon skaliert als 50 vor, ist die DecimalPipe mit angehängtem %-Suffix ehrlicher. Doppeltes Multiplizieren ergibt sonst 5,000 %.
Wie ändere ich die Locale global?
In der app.config.ts einmalig registerLocaleData(localeDe) aufrufen und einen Provider { provide: LOCALE_ID, useValue: ‘de-DE’ } ergänzen. Ab dann nutzen alle Pipes (auch DatePipe und CurrencyPipe) die deutsche Formatierung.
Warum sehen Zahlen auf Server und Client unterschiedlich aus?
Bei SSR rendert der Server möglicherweise mit einer anderen LOCALE_ID oder ohne registrierte Locale-Daten. Der Server muss dieselbe registerLocaleData-Konfiguration haben wie der Client — sonst hydriert Angular und die Werte springen plötzlich vom englischen auf das deutsche Format.
Was bedeutet digitsInfo '1.0-2' genau?
Mindestens 1 Vorkommastelle, mindestens 0 Nachkommastellen, höchstens 2 Nachkommastellen. 10.50 wird zu 10.5, 10 bleibt 10 (kein Padding mit Null). Für ausgerichtete Tabellen-Spalten ist ‘1.2-2’ die bessere Wahl.
Kann ich den Tausender-Trenner unterdrücken?
Mit der Pipe nicht direkt — sie folgt strikt den Locale-Regeln. Alternativen: eine eigene Pipe mit Intl.NumberFormat(locale, { useGrouping: false }), oder die Berechnung im TypeScript-Code als Property setzen und nur die Pipe für Nachkommastellen nutzen.
Funktionieren die Pipes mit BigInt?
Ja, seit Angular 16 akzeptieren DecimalPipe und PercentPipe bigint-Werte als Eingabe. Intern wird auf Intl.NumberFormat delegiert, das BigInt nativ unterstützt. Achtung: digitsInfo mit Nachkommastellen ist bei reinen Integer-BigInts wirkungslos.
Was passiert bei null oder undefined?
Beide Pipes sind defensiv: null und undefined werden zu null zurückgegeben, was im Template als leerer String erscheint. Du brauchst also kein @if davor zu setzen — die Pipe schluckt fehlende Werte still.
Welches Caching-Verhalten haben die Pipes?
Beide sind pure Pipes (Default). Angular ruft transform() nur dann erneut auf, wenn sich die Referenz des Eingabewerts ändert. Bei Signals, OnPush und unveränderlichen Daten ist das ideal — wiederholtes Rendern ohne neue Werte verursacht keine Berechnung.
Weiterführende Ressourcen
Externe Quellen
- DecimalPipe API – Angular.dev
- PercentPipe API – Angular.dev
- CurrencyPipe API – Angular.dev
- Intl.NumberFormat – MDN