Mert Özen Her Satırda Daha İleri
Frontend Angular — #09

Angular Pipe'ları: Tarih, Para Birimi, Türkçe Locale ve Custom Pipe Yazmak

Mert Özen 31 Eki 2026 7 dk 1 görüntülenme
Angular Pipe'ları: Tarih, Para Birimi, Türkçe Locale ve Custom Pipe Yazmak

date, currency, number ve titlecase gibi hazır pipe'lar, parametre ve zincirleme kullanım, registerLocaleData ile Türkçe format, custom pipe yazmak ve pure/impure farkının performansa etkisi.

Sürüm notu: Bu yazıdaki kod ve komutlar Angular 12 içindir.

Veri Aynı, Görünüş Farklı

Class'ında bir Date nesnesi var ve onu ekranda "12 Mayıs 2021" diye göstermek istiyorsun. Bir de 1450 sayısı var, "₺1.450,00" olarak görünmeli. İlk akla gelen, class'a formatDate(), formatPrice() gibi yardımcı metodlar yazmak. Üç component sonra aynı metodları kopyalayıp yapıştırdığını fark edersin.

Angular'ın bu iş için bir aracı var: pipe. Pipe, template'te bir değeri alıp dönüştürülmüş hâlini ekrana basan küçük bir fonksiyondur. Orijinal veri değişmez, sadece gösterimi değişir.

Musluğa takılan bir filtre gibi düşün. Şebekedeki su aynı kalıyor, bardağına gelen su filtreden geçmiş oluyor. Filtreyi değiştirirsen bardaktaki su değişir, şebeke yerinde durur.

İlk Pipe'lar: Harflerle Oynamak

Bir component'te şu iki property olsun:

export class CustomerCardComponent {
  customer = 'ayşe yılmaz';
  city = 'istanbul';
}

Template'te pipe'ı değerin sağına dikey çizgiyle eklersin:

<p>{{ customer | titlecase }}</p>   <!-- Ayşe Yılmaz -->
<p>{{ customer | uppercase }}</p>   <!-- AYŞE YILMAZ -->
<p>{{ city | uppercase }}</p>       <!-- ISTANBUL -->
<p>{{ city | titlecase }}</p>       <!-- Istanbul -->

| karakteri Angular'a "soldaki değeri sağdaki pipe'tan geçir" der. titlecase her kelimenin ilk harfini büyütür, uppercase hepsini büyütür, lowercase da hepsini küçültür.

Son iki satıra dikkat et. "ISTANBUL" ve "Istanbul" yazıyor, noktalı İ yok. Bu pipe'lar arka planda JavaScript'in toUpperCase() metodunu kullanır ve bu metod dilden habersizdir; küçük i'yi her zaman noktasız I yapar. Türkçe metinle çalışıyorsan bu ayrıntıyı aklında tut, yazının sonunda bu sorunu kendi pipe'ımızla çözeceğiz.

Parametreli Pipe'lar: Tarih, Sayı, Para

Bazı pipe'lar ayar alır. Parametreyi pipe adından sonra iki nokta üst üste ile verirsin. Bir sipariş özeti düşünelim:

export class OrderSummaryComponent {
  orderDate = new Date(2021, 4, 12, 14, 30);
  total = 1450;
  discount = 0.15;
  rating = 4.6666;
}
<p>{{ orderDate | date }}</p>                      <!-- May 12, 2021 -->
<p>{{ orderDate | date:'dd.MM.yyyy HH:mm' }}</p>   <!-- 12.05.2021 14:30 -->
<p>{{ total | number }}</p>                        <!-- 1,450 -->
<p>{{ rating | number:'1.1-2' }}</p>               <!-- 4.67 -->
<p>{{ discount | percent }}</p>                    <!-- 15% -->
<p>{{ total | currency:'TRY' }}</p>                <!-- TRY1,450.00 -->
<p>{{ total | currency:'TRY':'symbol-narrow' }}</p> <!-- ₺1,450.00 -->

date parametresiz kullanıldığında varsayılan bir format seçer. Kendi formatını dd, MM, yyyy, HH, mm gibi parçalarla kurabilirsin; MM ay, mm dakikadır, karıştırma.

number pipe'ındaki '1.1-2' ifadesi üç sayıdan oluşur: en az kaç tam sayı basamağı, en az kaç ondalık basamak ve en fazla kaç ondalık basamak. Yani 4.6666 en az bir, en fazla iki ondalıkla yuvarlanıp 4.67 olur.

currency ilk parametre olarak para birimi kodunu alır. İkinci parametre sembolün nasıl görüneceğini belirler. Sembol yerine "TRY" yazmasının sebebi, uygulamanın şu an İngilizce (ABD) ayarlarıyla çalışması. Virgülle noktanın da ters durduğunu fark etmişsindir. Buna birazdan geleceğiz.

Pipe'ları Zincirlemek

Bir pipe'ın çıktısını başka bir pipe'a verebilirsin. Soldan sağa okunur:

<p>{{ city | slice:0:3 | uppercase }}</p>      <!-- IST -->
<p>{{ orderDate | date:'MMMM' | lowercase }}</p> <!-- may -->

<li *ngFor="let item of items | slice:0:5">{{ item }}</li>

<pre>{{ order | json }}</pre>

slice, string'den ya da diziden bir parça keser; JavaScript'teki slice metodunun aynısı. İlk satırda önce şehrin ilk üç harfini alıp sonra büyütüyoruz. Daha önce gördüğümüz *ngFor ile birleşince "listenin ilk beş elemanını göster" demenin en kısa yolu olur.

json pipe'ı ise bir nesneyi okunabilir JSON metnine çevirir. Ekrana basmak için değil, geliştirirken "bu nesnenin içinde ne var?" sorusuna hızlı cevap için kullanılır. <pre> içine koyarsan girintili görünür.

Türkçe Locale: registerLocaleData

Tarihleri Türkçe ay adlarıyla, sayıları Türkiye'deki gibi binlik nokta ve ondalık virgülle göstermek için Angular'a Türkçe locale verisini tanıtmamız gerekiyor. app.module.ts dosyasını aç:

import { LOCALE_ID, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { registerLocaleData } from '@angular/common';
import localeTr from '@angular/common/locales/tr';

import { AppComponent } from './app.component';

registerLocaleData(localeTr);

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [{ provide: LOCALE_ID, useValue: 'tr' }],
  bootstrap: [AppComponent]
})
export class AppModule { }

İki adım var. registerLocaleData(localeTr) Türkçe'nin ay adlarını, gün adlarını ve sayı biçimlerini Angular'a yükler. LOCALE_ID satırı da uygulamaya "varsayılan dilin bu" der. Bu satırın providers içinde durmasının mantığını service'leri işlerken ilerleyen yazılarda detaylandıracağız.

Sadece LOCALE_ID verip veriyi kaydetmeyi unutursan uygulama açılırken "Missing locale data" hatası alırsın. İkisi birlikte gerekir. Şimdi aynı template'e tekrar bak:

<p>{{ orderDate | date }}</p>                    <!-- 12 May 2021 -->
<p>{{ orderDate | date:'d MMMM y, EEEE' }}</p>   <!-- 12 Mayıs 2021, Çarşamba -->
<p>{{ total | number }}</p>                      <!-- 1.450 -->
<p>{{ rating | number:'1.1-2' }}</p>             <!-- 4,67 -->
<p>{{ discount | percent }}</p>                  <!-- %15 -->
<p>{{ total | currency:'TRY' }}</p>              <!-- ₺1.450,00 -->

Ay ve gün adları Türkçe oldu, ayraçlar yer değiştirdi, yüzde işareti bile Türkçe kurala uyup sayının önüne geçti. İlk satırdaki "May" bir hata değil; Türkçe'de mayısın kısaltması zaten "May".

Bir tuzak daha: locale'i Türkçe yapmak varsayılan para birimini değiştirmez. Kod vermeden {{ total | currency }} yazarsan "$1.450,00" görürsün. Ya her seferinde 'TRY' yaz ya da providers listesine { provide: DEFAULT_CURRENCY_CODE, useValue: 'TRY' } ekle; DEFAULT_CURRENCY_CODE da @angular/core içinden gelir.

Kendi Pipe'ını Yazmak

Ürün açıklamaları uzun, kartta ilk kırk karakteri gösterip sonuna üç nokta koymak istiyoruz. CLI'ye bir pipe ürettirelim:

ng generate pipe truncate

Kısaltması ng g p truncate. CLI, component'ten farklı olarak klasör açmaz; src/app altına truncate.pipe.ts ile test dosyasını koyar ve pipe'ı app.module.ts içindeki declarations listesine ekler. Pipe'lar da component'ler gibi bir module'e kayıtlı olmak zorundadır. Oluşan iskeleti şöyle dolduruyoruz:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'truncate'
})
export class TruncatePipe implements PipeTransform {

  transform(value: string, limit = 20, suffix = '...'): string {
    if (value.length <= limit) {
      return value;
    }
    return value.slice(0, limit) + suffix;
  }

}

@Pipe decorator'ındaki name, template'te kullanacağın addır. PipeTransform interface'i tek bir metod ister: transform. İlk parametre her zaman pipe'ın solundaki değerdir. Sonrakiler, template'te iki nokta üst üste ile verdiğin parametrelerdir. Varsayılan değer verdiğimiz için ikisi de isteğe bağlı.

<p>{{ product.description | truncate:40 }}</p>
<p>{{ product.description | truncate:40:' →' }}</p>

İlk satır kırk karakterden sonrasını kesip üç nokta ekler, ikincisi üç nokta yerine ok koyar. Strict mod açık olduğu için template'te truncate'e sayı yerine metin verirsen derleme hatası alırsın; tipler template'te de kontrol edilir.

Pure ve Impure: Pipe Ne Zaman Yeniden Çalışır?

Stokta olan ürünleri süzen bir pipe yazalım:

@Pipe({ name: 'inStock' })
export class InStockPipe implements PipeTransform {
  transform(products: Product[]): Product[] {
    console.log('inStock çalıştı');
    return products.filter(p => p.stock > 0);
  }
}

Template'te *ngFor="let p of products | inStock" diye kullanıyoruz. Sonra bir butonla this.products.push(newProduct) yapıyoruz ve yeni ürün listede görünmüyor.

Sebep, pipe'ların varsayılan olarak pure olması. Pure bir pipe, sadece girdisi değiştiğinde yeniden çalışır. Sayı ya da metin için "değişti" demek değerin değişmesi demektir. Dizi ve nesne için ise referansın değişmesi. push aynı diziyi değiştirir, referans aynı kalır, Angular da pipe'ı tekrar çağırmaz.

İki çözüm var. Birincisi diziyi değiştirmek yerine yenisini oluşturmak: this.products = [...this.products, newProduct]. Referans değişir, pipe çalışır. İkincisi decorator'a pure: false yazıp pipe'ı impure yapmak. O zaman pipe her kontrol turunda çalışır; console'daki "inStock çalıştı" satırlarının nasıl aktığını görünce neden çekinmen gerektiğini anlarsın.

Pure pipe bir hesap makinesinin hafızası gibidir: aynı girdiyi verirsen tekrar hesaplamaz, sonucu hatırlar. Mümkün olduğunca pure kal, yeni referans üretmeyi alışkanlık hâline getir. Angular'ın kendi pipe'ları arasında impure olanlar da var, en bilineni async. Onu observable'larla birlikte ilerleyen yazılarda detaylandıracağız.

Küçük Bir Deneme

Yazının başındaki İstanbul sorununu çözme sırası sende. ng g p trUpper ile bir pipe üret. transform içinde değeri value.toLocaleUpperCase('tr-TR') ile büyüt. Template'te {{ 'istanbul' | uppercase }} ile {{ 'istanbul' | trUpper }} sonuçlarını yan yana koy ve farkı gör.

Sonra bir ürün dizisi hazırla, her ürüne name, price ve addedAt alanı ver. Listede adı trUpper, fiyatı currency:'TRY', tarihi date:'d MMMM y' ile göster. Önce Türkçe locale olmadan, sonra registerLocaleData ekleyerek bak.

Bu pipe'lar tek bir component'in template'inde gayet iyi çalışıyor. Ama ürün listesini birden fazla component'in görmesi, sepete eklenen ürünü başka bir component'in bilmesi gerektiğinde veriyi nerede tutacağız? Bir sonraki yazıda service'leri ve Angular'ın dependency injection sistemini konuşacağız.