Drei Pipes, die auf den ersten Blick trivial aussehen — und doch viele Fallstricke haben: uppercase, lowercase und titlecase. Sie kommen ohne Argumente aus, sind alle pure und alle aus @angular/common. Wo die ersten beiden simple Wrapper um native String-Methoden sind, hat die TitleCasePipe ein Eigenleben mit überraschenden Ergebnissen für Eigennamen, Akronyme und nicht-westliche Sprachen. Dieser Artikel zeigt, wo die Pipes glänzen — und wo CSS oder eine eigene Pipe die bessere Wahl ist.

Die drei Case-Pipes

Aus @angular/common stammen drei Pipes, die ausschließlich die Schreibweise eines Strings ändern:

  • UpperCasePipe — Template: | uppercase — alles GROSS.
  • LowerCasePipe — Template: | lowercase — alles klein.
  • TitleCasePipe — Template: | titlecase — Erster Buchstabe Jedes Wortes Gross.

Alle drei sind pure (Caching), erwarten keine Argumente, akzeptieren string | null | undefined und geben bei null/undefined ebenfalls null zurück. Im Standalone-Setup importierst du sie einzeln in das imports-Array deiner Komponente — CommonModule ist nicht nötig.

TypeScript case-pipes-hello.ts
import { Component } from '@angular/core';
import { UpperCasePipe, LowerCasePipe, TitleCasePipe } from '@angular/common';

@Component({
    selector: 'app-cases',
    imports: [UpperCasePipe, LowerCasePipe, TitleCasePipe],
    template: `
        <p>{{ 'hallo welt' | uppercase }}</p>   <!-- HALLO WELT -->
        <p>{{ 'HALLO WELT' | lowercase }}</p>   <!-- hallo welt -->
        <p>{{ 'hallo welt' | titlecase }}</p>   <!-- Hallo Welt -->
    `
})
export class CasesComponent {}

UpperCasePipe

Die UpperCasePipe ist intern ein dünner Wrapper um String.prototype.toUpperCase(). Sie nimmt keine Locale-Information entgegen und arbeitet rein UTF-16-basiert — sprachspezifische Casing-Regeln (siehe Türkisch weiter unten) werden ignoriert.

HTML uppercase.html
<p>{{ 'angular' | uppercase }}</p>            <!-- ANGULAR -->
<p>{{ 'straße' | uppercase }}</p>             <!-- STRAßE  (NICHT STRASSE!) -->
<p>{{ 'café résumé' | uppercase }}</p>        <!-- CAFÉ RÉSUMÉ -->
<p>{{ 'müller-lüdenscheidt' | uppercase }}</p><!-- MÜLLER-LÜDENSCHEIDT -->

Beachte das ß — die ECMAScript-Spezifikation macht aus 'ß'.toUpperCase() weiterhin 'ß', nicht das offizielle Großbuchstaben-Eszett und auch nicht SS. Wer das braucht, muss eine eigene Pipe mit toLocaleUpperCase('de') schreiben.

LowerCasePipe

Symmetrisch dazu wrappt LowerCasePipe die native String.prototype.toLowerCase(). Auch hier: keine Locale, keine Argumente, kein Sonderfall. Bei null/undefined Rückgabe null.

HTML lowercase.html
<p>{{ 'HELLO WORLD' | lowercase }}</p>   <!-- hello world -->
<p>{{ 'iOS Push' | lowercase }}</p>      <!-- ios push -->
<p>{{ null | lowercase }}</p>            <!-- (leer) -->

Typischer Anwendungsfall: Case-insensitive Suche. Statt im TypeScript-Code mit String.prototype.toLowerCase() zu hantieren, kann man Eingabewerte direkt im Template normalisieren.

TitleCasePipe — was tut sie genau?

Hier wird es spannend. Die TitleCasePipe macht nicht echtes „Title Case“ im journalistischen Sinn (Bindewörter klein), sondern einen rein mechanischen Schritt:

  1. Der String wird an Whitespace (Space, Tab, Linefeed) in „Wörter“ geschnitten.
  2. Vom ersten Zeichen jedes Wortes wird toUpperCase() gerufen.
  3. Vom Rest des Wortes wird toLowerCase() gerufen.
HTML titlecase-basic.html
<p>{{ 'hello world' | titlecase }}</p>            <!-- Hello World -->
<p>{{ 'this is a TEST' | titlecase }}</p>         <!-- This Is A Test -->
<p>{{ 'müller meier' | titlecase }}</p>           <!-- Müller Meier -->

Wichtig: Bindestrich, Komma, Pipe-Zeichen trennen NICHT — nur Whitespace. Das verursacht im Alltag die meisten Überraschungen.

TitleCasePipe-Pitfalls

Die mechanische Logik führt bei Eigennamen und Akronymen schnell zu falschen Ergebnissen:

| Eingabe | Ausgabe | titlecase | Korrekt wäre | | --- | --- | --- | | iPhone | Iphone | iPhone | | McDonald | Mcdonald | McDonald | | IBM | Ibm | IBM | | NASA-Mission | Nasa-mission | NASA-Mission | | it's non-trivial | It's Non-trivial | It's Non-Trivial | | foo-vs-bar | Foo-vs-bar | Foo-vs-Bar | | one,two,three | One,two,three | One,Two,Three | | true|false | True|false | (je nach Kontext) |

Tipp für eine eigene Pipe: Eine simple Map mit Sonderfällen ({ 'iphone': 'iPhone', 'ibm': 'IBM' }) reicht oft schon, um 90 % der Fehler abzufangen — Lookup vor der mechanischen Title-Case-Logik.

Locale-Verhalten der toUpperCase/toLowerCase

JavaScript kennt zwei Arten der Case-Konvertierung:

  • toUpperCase() / toLowerCase() — Locale-unabhängig, UTF-16-basiert.
  • toLocaleUpperCase(locale) / toLocaleLowerCase(locale) — sprachsensitiv.

Die Angular-Pipes nutzen ausschließlich die erste, locale-unabhängige Variante. Für die meisten westeuropäischen Sprachen ist das egal — für Türkisch (tr-TR) aber nicht:

TypeScript turkish-locale.ts
// Ohne Locale (Pipe-Verhalten)
'i'.toUpperCase();                  // 'I'   — falsch für Türkisch
'I'.toLowerCase();                  // 'i'   — falsch für Türkisch

// Mit Türkisch-Locale (was Angular NICHT macht)
'i'.toLocaleUpperCase('tr-TR');     // 'İ'   — punktiertes I
'I'.toLocaleLowerCase('tr-TR');     // 'ı'   — punktloses i

Konsequenz: Wer eine türkische App baut und auf korrekte Casing-Regeln angewiesen ist, kann die Standard-Pipes nicht verwenden. Lösung: eine kleine Custom-Pipe, die toLocaleUpperCase(LOCALE_ID) aufruft und die LOCALE_ID per inject() zieht.

Praxis: Suchfeld mit Case-Insensitive-Filter

Ein klassischer Einsatzort für LowerCasePipe: ein Filter über eine Liste, der unabhängig von Groß-/Kleinschreibung arbeiten soll. Statt im Code zu normalisieren, geht das deklarativ:

TypeScript search-filter.component.ts
import { Component, signal, computed } from '@angular/core';
import { LowerCasePipe } from '@angular/common';
import { FormsModule } from '@angular/forms';

@Component({
    selector: 'app-search-filter',
    imports: [LowerCasePipe, FormsModule],
    template: `
        <input [(ngModel)]="query" placeholder="Suche…" />

        <p>Du suchst: {{ query() | lowercase }}</p>

        <ul>
            @for (name of filtered(); track name) {
                <li>{{ name }}</li>
            }
        </ul>
    `
})
export class SearchFilterComponent {
    query = signal('');
    names = signal(['Angular', 'Astro', 'React', 'Vue', 'Svelte']);

    filtered = computed(() => {
        const q = this.query().toLowerCase();
        return this.names().filter(n => n.toLowerCase().includes(q));
    });
}

Im Template normalisiert die Pipe den Anzeige-String, im computed() macht die Logik die eigentliche Filterung. Beides bleibt sauber getrennt.

Wann Pipe vs. CSS text-transform?

Für reine UI-Anzeige ist CSS fast immer die bessere Wahl. CSS text-transform: uppercase arbeitet auf der Render-Ebene, ändert nicht den DOM-String und respektiert (in modernen Browsern) sogar die Sprache via lang-Attribut.

| Aspekt | Pipe (| uppercase) | CSS (text-transform: uppercase) | Intl-API | | --- | --- | --- | --- | | Verändert DOM-String | Ja | Nein | Ja (im JS-Code) | | Render-Cost | gering, aber vorhanden | praktisch null | gering | | Locale-Sensitiv | nein | ja (via lang) | ja | | Aria-Label-tauglich | ja | nein (nur visuell) | ja | | Kopieren liefert | Großschreibung | Originalschreibung | Großschreibung | | Wartbarkeit | logisch im Template | rein im CSS | logisch im Code |

Faustregel: Wenn der Wert nur visuell groß sein soll (Überschriften, Buttons), nimm CSS. Wenn der Wert auch außerhalb der Anzeige (z. B. in einem aria-label, einer Suchabfrage oder einem Tooltip) groß sein muss, nimm die Pipe.

Praxis-Notizen zu den Case-Pipes

TitleCasePipe ist nicht smart

Sie macht „erstes Zeichen jedes Wortes groß, Rest klein“ — keine echten Title-Case-Regeln. Bindewörter (and, or, the) werden nicht klein gehalten, Akronyme nicht erhalten. Für redaktionelle Headlines lieber eine eigene Pipe oder den korrekten Wert direkt aus dem CMS pflegen.

Case-Pipes nutzen NICHT toLocaleUpperCase

Sprachspezifische Casing-Regeln werden ignoriert. Türkisch (i/İ und ı/I), Litauisch und einige andere Locales bekommen falsche Ergebnisse. Wer das braucht, schreibt eine kurze Custom-Pipe, die LOCALE_ID injiziert und toLocaleUpperCase(locale) aufruft.

CSS text-transform ist für UI meist besser

Es verändert den DOM-String nicht, kostet keinen Re-Render-Aufwand und kann in modernen Browsern via lang-Attribut sogar Locale-sensitiv arbeiten. Pipes lohnen sich nur, wenn der formatierte String auch außerhalb des sichtbaren Textes (z. B. als aria-label) gebraucht wird.

Pure pipes — Caching greift bei identischen Inputs

Alle drei Pipes sind pure. In einer Schleife mit @for und vielen identischen Strings wird transform() nur einmal pro Referenz aufgerufen. Bei OnPush und Signals ist das praktisch kostenlos — Sorgen über Performance sind unbegründet.

ASCII-Akronym-Falle

‘NASA’ | titlecase wird zu ‘Nasa’, ‘CEO’ | titlecase zu ‘Ceo’. Wer Akronyme in benutzergenerierten Daten hat, sollte vor dem Pipe-Aufruf eine Whitelist anwenden oder Title-Case komplett vermeiden.

Standalone-Import — einzeln, nicht über CommonModule

Im modernen Standalone-Setup importierst du die Pipes einzeln: imports: [UpperCasePipe, LowerCasePipe, TitleCasePipe]. Das spart Bundle-Größe und macht Abhängigkeiten explizit — der frühere CommonModule-Import ist nicht mehr nötig.

null und undefined sind safe

Alle drei Pipes geben bei null oder undefined kommentarlos null zurück, was im Template als leerer String erscheint. Du brauchst kein @if davor — die Pipe macht den Schutz selbst.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Pipes

Zur Übersicht