Mert Özen Her Satırda Daha İleri
Frontend Angular Material ve CDK — #02

Material Form Component'leri: mat-form-field, matInput, mat-select, mat-autocomplete ve mat-datepicker

Mert Özen 15 Kas 2026 14 dk 6 görüntülenme
Material Form Component'leri: mat-form-field, matInput, mat-select, mat-autocomplete ve mat-datepicker

mat-error, varsayılan ErrorStateMatcher yüzünden yalnız alan touched olunca ya da submit sonrası görünür; Signal Forms'ta ShowOnDirty matcher'ı boş zorunlu alanın hatasını hiç göstermez. Bookstore'un kurumsal sipariş form'unda compareWith'siz mat-select'in boş görünmesini, debounce ile 8 request'in 1'e inmesini, native date adapter'ın 05.12.2026'yı 12 Mayıs okumasını, date-fns ile Türkçe tarihi ve 15 Aralık'ın UTC'de 14'üne kaymasını ölçüyoruz.

Sürüm notu: Bu yazıdaki örnekler Angular 22.2 (TypeScript 6.0, Signal Forms), Angular Material ve CDK (Component Dev Kit; Angular ekibinin stilsiz davranış library'si) 22.2.7, @angular/material-date-fns-adapter 22.2.7, date-fns 4.4 ve Node.js 24 ile denenmiştir; tarayıcı davranışı Playwright 1.65 ile headless Chromium'da, saat dilimi Europe/Istanbul olarak gözlenmiştir. Bookstore.Api ölçümlerde SQL Server yerine SQLite ile çalıştı.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) yönetim paneline kurumsal sipariş form'u ekliyoruz: okul ve kütüphanelerin toplu siparişi, en az 20 adet. Form'da kurum birimi seçimi, sunucuda arama yapan bir kitap alanı, adet, teslim tarihi ve iletişim e-postası var. Üç soru: Hiçbir alana dokunmadan "Create order"a basınca hangi alanların altında kırmızı hata görünür? Material'ın varsayılan date adapter'ı Türkçe locale ile çalışırken kullanıcı tarih alanına "05.12.2026" yazarsa model'e hangi gün girer? Takvimden 15 Aralık'ı seçip model'i JSON.stringify ile sunucuya gönderirsen deliveryDate ne olur?

Form'un İskeleti: mat-form-field, Label, Hint ve Error

Kurum birimleri için API'ye küçük bir endpoint ekledik. Institutional module'ün kendi tabloları henüz yok; endpoint demo kurumun birimlerini sabit bir listeden dönüyor:

// Şimdilik salt okunur: Institutional module'ün kendi tabloları gelene kadar demo kurumun birimleri
public static class InstitutionalModule
{
    private static readonly InstitutionUnitView[] DemoUnits =
    [
        new(1, "Main Campus Library", "Campus"),
        new(2, "North Campus Library", "Campus"),
        new(3, "Science Department", "Department"),
        new(4, "Literature Department", "Department"),
        new(5, "Primary School Branch", "Branch")
    ];

    public static IEndpointRouteBuilder MapInstitutionalEndpoints(this IEndpointRouteBuilder app)
    {
        app.MapGet("/api/institutions/{institutionId:int}/units", GetUnits).WithTags("Institutional");
        return app;
    }

    private static Results<Ok<InstitutionUnitView[]>, NotFound> GetUnits(int institutionId) =>
        institutionId == 1 ? TypedResults.Ok(DemoUnits) : TypedResults.NotFound();
}

Form'un model'i bir signal, kuralları Signal Forms schema'sında. Angular 22 ile stabil olan Signal Forms'un kendisini Angular serisinin "Angular 22: Varsayılan OnPush, Stable Signal Forms ve Angular 12 ile Proje Farkı" yazısı anlatıyor; burada Material component'leriyle birlikte nasıl davrandığına bakıyoruz. Material 22.2.0'ın CHANGELOG'unda "form-field: support signal forms in MatFormFieldControl" satırı var: [formField] directive'i matInput, mat-select ve datepicker input'una doğrudan bağlanıyor.

protected readonly orderForm = form(this.model, path => {
  // required() boş array'i değer sayıyor; en az bir birimi açıkça iste
  minLength(path.units, 1, { message: 'Choose at least one unit.' });
  validate(path.book, ({ value }) =>
    typeof value() === 'string' ? { kind: 'bookNotChosen', message: 'Pick a book from the list.' } : undefined);
  min(path.quantity, 20, { message: 'Institutional orders start at 20 copies.' });
  required(path.deliveryDate, { message: 'Choose a delivery date.' });
  minDate(path.deliveryDate, this.tomorrow, { message: 'The earliest delivery date is tomorrow.' });
  required(path.contactEmail, { message: 'Enter a contact e-mail.' });
  email(path.contactEmail, { message: 'Enter an e-mail address like buyer@example.com.' });
});
<mat-form-field>
  <mat-label>Contact e-mail</mat-label>
  <mat-icon matPrefix>mail</mat-icon>
  <input matInput type="email" autocomplete="email" [formField]="orderForm.contactEmail"
         [errorStateMatcher]="whileTyping" />
  <mat-error>{{ firstError(orderForm.contactEmail) }}</mat-error>
</mat-form-field>

mat-form-field bir kabuk: içindeki control'ün durumuna göre label'ı yukarı taşır, mat-hint ile mat-error arasında geçiş yapar ve matPrefix ile matIconSuffix slot'larını yerleştirir. mat-error her zaman template'te durur; görünüp görünmeyeceğine control'ün error state'i karar verir. Varsayılan appearance fill (kaynakta DEFAULT_APPEARANCE = 'fill'); biz sayfadaki bütün alanları her birine attribute yazmak yerine tek bir provider ile outline yaptık:

providers: [
  provideDateFnsAdapter(),
  { provide: MAT_DATE_LOCALE, useValue: tr },
  // Her alana appearance="outline" yazmak yerine sayfadaki bütün alanların görünümü tek yerde
  { provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'outline' } },
],

02-order-form-tr.png

mat-error Ne Zaman Görünür?

Tahminin ilk sorusu. Playwright ile önce sayfayı açtık, sonra e-posta alanına girip Tab ile çıktık, en son boş form'u gönderdik:

on load        : []
e-mail blurred : ["Enter a contact e-mail."]
after submit   : ["Choose at least one unit.","Pick a book from the list.","Choose a delivery date.","Enter a contact e-mail."]

Sayfa açıldığında form geçersiz ama hiçbir hata görünmüyor; bu bug değil, Material'ın varsayılan ErrorStateMatcher'ının kuralı. Kaynakta iki method var: Reactive Forms için isErrorState(control, form) "geçersiz ve (touched ya da form submit edilmiş)" diyor; Signal Forms için 22.2'de eklenen isSignalErrorState(field) ise yalnız "geçersiz ve touched" diyor, çünkü Signal Forms'ta parent form'un submitted bilgisi yok. Submit sonrası hataların görünmesini Signal Forms'un submit() function'ı sağlıyor: ölçümde submit'ten sonra bütün alanlar touched'dı. Adet alanı listede yok, çünkü başlangıç değeri 20 ve geçerli.

Bu ölçümün ilk hâlinde listede birim alanı da yoktu. Schema'da required(path.units) vardı ve submit sonrası alanların durumu şuydu (T: touched, I: invalid):

submit         units:T- book:TI quantity:T- deliveryDate:TI contactEmail:TI | mat-error: ["Pick a book from the list.","Choose a delivery date.","Enter a contact e-mail."]

Birim alanı touched ama geçersiz değil: required boş array'i bir değer sayıyor. Çoklu seçimde "en az bir tane" kuralı minLength(path.units, 1) ile yazılmalı.

Bazı alanlarda hatayı yazarken göstermek isteyebilirsin. E-posta için Material'ın hazır ShowOnDirtyErrorStateMatcher'ını denedik ve yeni bir hata ürettik:

on load        : []
e-mail blurred : []
after submit   : ["Choose at least one unit.","Pick a book from the list.","Choose a delivery date."]

Bu matcher'ın Signal Forms tarafı "geçersiz ve dirty" diyor. Boş bırakılan e-posta hiç dirty olmadığı için "Enter a contact e-mail." submit sonrasında bile görünmedi. Reactive Forms'ta aynı matcher form submitted iken hatayı gösteriyor; Signal Forms'ta o bilgi yok. Çözüm iki koşulu birleştiren küçük bir matcher:

// ShowOnDirtyErrorStateMatcher tek başına boş alandaki "required" hatasını submit'ten sonra bile gizler:
// submit() alanları dirty değil, touched yapar
class WhileTypingErrorStateMatcher extends ErrorStateMatcher {
  override isSignalErrorState(field: Field<unknown> | null): boolean {
    const state = field?.();
    return !!state && state.invalid() && (state.dirty() || state.touched());
  }
}
quantity "5", no blur      []
e-mail "nevin@", no blur   ["Institutional orders start at 20 copies.","Enter an e-mail address like buyer@example.com."]
after Tab                  ["Institutional orders start at 20 copies.","Enter an e-mail address like buyer@example.com."]

Adet alanına 5 yazınca hata hemen gelmedi, e-postaya geçince geldi; e-postanın hatası ise yazarken göründü. İki davranış aynı form'da bilerek farklı.

mat-select: Çoklu Seçim ve compareWith

Siparişin teslim edileceği birimler çoklu bir mat-select. Kullanıcıya kolaylık olsun diye son siparişte seçilen birimleri localStorage'da tutuyor ve form'u onlarla açıyoruz:

<mat-select [formField]="orderForm.units" multiple [compareWith]="sameUnit">
  @for (unit of units.value(); track unit.id) {
    <mat-option [value]="unit">{{ unit.name }}</mat-option>
  }
</mat-select>
// Seçenekler her yüklemede API'den yeni object olarak geliyor: birimleri referansla değil id ile karşılaştır
protected readonly sameUnit = (a: InstitutionUnit | null, b: InstitutionUnit | null) => a?.id === b?.id;

Sayfayı yeniden yükleyip select'in gösterdiği metni ve model'deki değeri okuduk; önce compareWith olmadan, sonra onunla:

# compareWith yok
after reload  : "" | model units: Main Campus Library, Science Department
# compareWith="sameUnit"
after reload  : "Main Campus Library, Science Department" | model units: Main Campus Library, Science Department

İlk satır sinsi bir hata: model'de iki birim var, form geçerli, ama kullanıcı boş bir select görüyor ve büyük ihtimalle birimleri yeniden seçiyor. mat-select varsayılan olarak === ile karşılaştırıyor; localStorage'dan parse edilen object'lerle API'den gelen object'ler aynı referans değil. Value'yu object yerine id yapmak da bir çözüm; object tutmak istiyorsan compareWith şart.

mat-autocomplete ile Sunucuda Arama

Kitap alanı katalogda sunucu tarafında arıyor. Input'un değeri yazılırken bir string, listeden seçilince bir BookSummary object'i; schema'daki validate kuralı "listeden bir kitap seçilmiş mi" sorusunu bu farka bakarak cevaplıyor. Arama, model'deki string'den türetilen bir signal'i RxJS'e çeviriyor:

// Sunucuda arama: son tuştan 300 ms sonra, en az 2 karakter, eski request iptal edilir
private readonly query = computed(() => {
  const value = this.model().book;
  return typeof value === 'string' ? value.trim() : '';
});
protected readonly matches = toSignal(
  toObservable(this.query).pipe(
    debounceTime(300),
    distinctUntilChanged(),
    switchMap(search => search.length < 2
      ? of([])
      : this.http.get<PagedResult<BookSummary>>('/api/books', { params: { search, pageSize: 8 } }).pipe(
          map(result => result.items),
          catchError(() => of([])))),
  ),
  { initialValue: [] },
);
<input matInput [formField]="orderForm.book" [matAutocomplete]="bookList" />
<mat-autocomplete #bookList [displayWith]="bookTitle">
  @for (book of matches(); track book.isbn) {
    <mat-option [value]="book">{{ book.title }} · {{ book.author }}</mat-option>
  }
</mat-autocomplete>

"The Hobbit"i 60 ms arayla tuş tuş yazdırıp /api/books'a giden request'leri saydık:

# debounceTime(300) yok
book requests : 8 ?search=Th&pageSize=8 ?search=The&pageSize=8 ?search=The H&pageSize=8 ?search=The Ho&pageSize=8 ?search=The Hob&pageSize=8 ?search=The Hobb&pageSize=8 ?search=The Hobbi&pageSize=8 ?search=The Hobbit&pageSize=8
# debounceTime(300)
book requests : 1 ?search=The Hobbit&pageSize=8

Debounce olmadan ikinci harften itibaren her harf bir request; "The " ile "The" aynı sorguya düştüğü için distinctUntilChanged birini eledi. switchMap eski request'leri iptal ettiği için yanlış sırada gelen bir cevap listeyi bozmaz, ama sunucu yine sekiz sorgu çalıştırır. Zamanla çalışan operator'leri RxJS serisinin "Zamanla Çalışan Operator'ler ve Scheduler'lar: debounceTime, throttleTime, auditTime, timeout ve Polling" yazısı ayrıntılı anlatıyor. displayWith ise seçilen object'in input'ta nasıl yazılacağını belirliyor; o olmasa input'ta "[object Object]" görünür.

mat-datepicker: Türkçe Tarih ve Pazartesi

İlk denemede Material'ın kendi provideNativeDateAdapter()'ını MAT_DATE_LOCALE: 'tr-TR' ile kullandık. Takvim Türkçe ve Pazartesi'yle başlıyor: native adapter haftanın ilk gününü tarayıcının Intl.Locale week bilgisinden alıyor. Sorun yazılan tarihte çıktı:

# provideNativeDateAdapter() + 'tr-TR'
typed 05.12.2026 input="12.05.2026" model=Tue May 12 2026 touched=true errors=["matDatepickerMin","minDate"] | mat-error: []
typed 15.12.2026 input="15.12.2026" model=null touched=true errors=["matDatepickerParse","required"] | mat-error: []
typed 2026-12-15 input="15.12.2026" model=Tue Dec 15 2026 touched=true errors=[] | mat-error: []
calendar         ARA 2026 | P S Ç P C C P

Tahminin ikinci sorusunun cevabı: 12 Mayıs. Native adapter görüntülemeyi Intl.DateTimeFormat ile locale'e göre yapıyor ama parse için Date.parse kullanıyor (kaynakta new Date(Date.parse(value))); Chromium "05.12.2026"yı ay.gün.yıl okudu, "15.12.2026"yı hiç okuyamadı. Gün 12'den küçükse sessizce yanlış tarih, büyükse parse hatası. İkinci gözlem: datepicker'ın kendi validator'ları (matDatepickerParse, matDatepickerMin) Signal Forms'un hata listesine giriyor ama message'sız geliyor; bizim firstError'ımız boş string döndüğü için mat-error boş kaldı.

Türkçe tarih için Material'ın date-fns adapter'ına geçtik. Adapter'ın varsayılan formatı date-fns'in P'si, yani locale'in kısa tarih formatı; tr locale'inde bu gün.ay.yıl ve haftanın ilk günü Pazartesi (weekStartsOn: 1):

npm install @angular/material-date-fns-adapter@22.2.7 date-fns@4.4.0
const datepickerMessages: Record<string, string> = {
  matDatepickerParse: 'Enter the date as DD.MM.YYYY.',
  matDatepickerMin: 'The earliest delivery date is tomorrow.',
};

protected firstError(field: { (): { errors(): { kind: string; message?: string }[] } }): string {
  const error = field().errors()[0];
  // Datepicker'ın kendi validator'ları (parse, min, max, filter) message'sız gelir
  return error?.message ?? datepickerMessages[error?.kind ?? ''] ?? '';
}
# provideDateFnsAdapter() + date-fns tr locale
typed 05.12.2026 input="05.12.2026" model=Sat Dec 05 2026 touched=true errors=[] | mat-error: []
typed 15.12.2026 input="15.12.2026" model=Tue Dec 15 2026 touched=true errors=[] | mat-error: []
typed 31.02.2026 input="31.02.2026" model=null touched=true errors=["matDatepickerParse","required"] | mat-error: ["Enter the date as DD.MM.YYYY."]
typed 2026-12-15 input="15.12.2026" model=Tue Dec 15 2026 touched=true errors=[] | mat-error: []
calendar         ARA 2026 | P S Ç P C C P

Doğru parse'ın bir bedeli var. Sayfa chunk'ı native adapter ile 340.55 kB (64.72 kB transfer), date-fns ve Türkçe locale ile 389.80 kB (75.39 kB transfer) oldu. Yaklaşık 11 kB transfer, yanlış teslim tarihiyle çıkan bir siparişin yanında küçük.

Saat Dilimi Kayması

Tahminin üçüncü sorusu. Datepicker, seçilen günü yerel saatle gece yarısı olarak bir Date'e koyuyor. Model'i olduğu gibi JSON.stringify ile göndermek toISOString() çağırıyor ve İstanbul'da (UTC+3) seçilen 15 Aralık bir önceki güne kayıyor:

JSON.stringify(model.deliveryDate): "2026-12-14T21:00:00.000Z"
timezone: Europe/Istanbul

Sunucu bu değeri UTC'de tarih olarak okursa sipariş 14 Aralık'a yazılır. Teslim tarihi bir an değil, takvimde bir gün; request'e saatsiz bir tarih olarak koyduk:

function toDateOnly(date: Date): string {
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  return `${date.getFullYear()}-${month}-${day}`;
}
{
  "unitIds": [
    1,
    3
  ],
  "isbn": "9780261102217",
  "quantity": 40,
  "deliveryDate": "2026-12-15",
  "contactEmail": "nevin@example.com",
  "notes": ""
}

Sunucu tarafında bunun karşılığı DateOnly. Saat dilimleri ve Intl ile biçimlendirme JavaScript serisinin "JavaScript İleri Tarih İşlemleri: Saat Dilimleri, UTC ve Intl ile Biçimlendirme" yazısında.

Uzayan Textarea: cdkTextareaAutosize mı, field-sizing mi?

Depo notları alanı içerik kadar uzasın istedik. İlk denemede CDK'nın cdkTextareaAutosize directive'ini cdkAutosizeMinRows="2" ve cdkAutosizeMaxRows="6" ile kullandık ve 1, 4 ve 10 satır yazıp yüksekliği ölçtük:

cdkTextareaAutosize  1 lines: height 30px, scrollable false
cdkTextareaAutosize  4 lines: height 90px, scrollable true
cdkTextareaAutosize 10 lines: height 90px, scrollable true

Dört satır, altı satırlık sınıra rağmen scroll'a düştü. Directive bir satırın yüksekliğini textarea'nın bir kopyasını ölçerek buluyor; ölçüm anını yakaladığımızda kopyanın fontu tarayıcının varsayılanıydı:

clone: clientHeight=15 font="13.3333px monospace" parentFont="16px / 24px Roboto" sheets=21 fontsStatus=loading

Bir satırı 24px yerine 15px saydı ve en fazla 6 × 15 = 90px'e izin verdi. Pencere yeniden boyutlanınca directive ölçümü tekrarlıyor ve 24px'i buluyor; ama kullanıcı sayfayı açtığında sınır yanlış. Aynı işi CSS'in field-sizing property'si ile yaptık:

// İçeriğiyle birlikte 2 ile 6 satır arasında uzar (Baseline 2026: Chrome 123, Firefox 152, Safari 26.2)
.notes {
  field-sizing: content;
  min-height: 2lh;
  max-height: 6lh;
}
field-sizing  1 lines: height 48px, scrollable false
field-sizing  4 lines: height 96px, scrollable false
field-sizing 10 lines: height 144px, scrollable true

MDN'e (Mozilla Developer Network; web teknolojilerinin başvuru dokümantasyonu) göre field-sizing Haziran 2026'dan beri Baseline. Desteklemeyen eski bir tarayıcıda textarea sabit yükseklikte kalıyor ve kullanıcı yine yazabiliyor; JS'li çözümün yanlış ölçümünden daha kabul edilebilir bir sonuç. Eski tarayıcı desteği şartsa cdkTextareaAutosize hâlâ seçenek, ama ilk ölçümünü kendi sayfanda doğrula.

Test: Harness ile Form Davranışı

Bu yazıdaki üç kararı teste bağladık: compareWith ile geri gelen birimler, submit sonrası görünen hatalar (e-postanın kendi matcher'ı dahil) ve Türkçe tarih ile saatsiz request. Testler Material'ın select, form-field, button ve datepicker harness'larını kullanıyor:

it('reads Turkish dates as day.month.year', async () => {
  const { fixture, loader } = await render();
  const year = new Date().getFullYear() + 1;
  const date = await loader.getHarness(MatDatepickerInputHarness);
  await date.setValue(`05.12.${year}`);
  await date.blur();

  const picked = fixture.componentInstance['model']().deliveryDate as Date;
  expect([picked.getDate(), picked.getMonth() + 1, picked.getFullYear()]).toEqual([5, 12, year]);
  expect(await date.getValue()).toBe(`05.12.${year}`);
});
 ✓ src/app/admin/institutional-order/institutional-order-page.spec.ts > InstitutionalOrderPage > selects the last used units again although the options are new objects
 ✓ src/app/admin/institutional-order/institutional-order-page.spec.ts > InstitutionalOrderPage > shows the errors of untouched fields only after submit
 ✓ src/app/admin/institutional-order/institutional-order-page.spec.ts > InstitutionalOrderPage > reads Turkish dates as day.month.year
 ✓ src/app/admin/institutional-order/institutional-order-page.spec.ts > InstitutionalOrderPage > sends the picked day as a date, not as UTC midnight
 Test Files  4 passed (4)
      Tests  8 passed (8)

Tarih testinde yılı sabit yazmadık; "yarından önce olamaz" kuralı yüzünden sabit bir tarih birkaç ay sonra testi kırardı.

Üretimde Dikkat

Form sayfasını ayrı yükle. Form-field, select, autocomplete, datepicker, Signal Forms ve date-fns birlikte 385.24 kB'lık (74.45 kB transfer) bir chunk ediyor; sayfayı loadComponent ile yükleyince dashboard'a girene bu kod inmiyor. Date adapter'ı da uygulama seviyesinde değil, bu component'in providers'ında verdik. Yine de bedelin bir kısmı panelin ortak chunk'ına yansıdı: admin-routes 173.01 kB'tan 232.44 kB'a çıktı. Build stats'ına göre bunun büyük kısmı form sayfasıyla paylaşılan kod, örneğin @angular/forms'tan 38.9 kB; build bu paylaşılan kodu her zaman önce yüklenen parent chunk'a koymuş. Bundle bütçesini sayfa sayfa değil, kullanıcının gerçekten indirdiği chunk zinciriyle ölç.

Hata mesajlarını bir yerde topla. Schema'daki kurallar message taşıyor, datepicker'ın validator'ları taşımıyor; hepsini tek bir function'dan geçirmek boş mat-error'ları engelliyor. Mesaj eksikse mat-error görünür ama boş kalır ve alan sebepsiz kırmızı olur.

Accessibility'yi ölç. Boş ve zorunlu e-posta alanında submit sonrası aria-invalid attribute'u yoktu, aria-describedby hata mesajına bağlıydı: Material boş zorunlu alanlarda aria-invalid'i bilerek yazmıyor. Signal Forms'un tek başına aria-invalid yazmadığını Accessibility (a11y) serisinin "Angular'da Accessible Component'ler: Angular Aria, CDK a11y, Material ve Kendi Combobox'ını Yazmamak" yazısında ölçmüştük; Material'ın form-field'ı bu bağlantıyı senin yerine kuruyor.

Sunucu da doğrulasın. Buradaki kurallar kullanıcıya erken geri bildirim için; 20 adet sınırı ve tarih kuralı sipariş API'sinde de olmalı.

Sık Düşülen Tuzaklar

Birincisi, mat-error'un yalnız touched ya da submit sonrası görünmesini bug sanmak. Varsayılan matcher'ın kuralı bu; değiştirmek istiyorsan Signal Forms'ta dirty ile touched'ı birlikte düşün, yoksa boş alanın hatası hiç görünmez.

İkincisi, object değerli mat-select'te compareWith'i unutmak. Model dolu, ekran boş.

Üçüncüsü, Türkçe tarih için native adapter'a güvenmek. Görüntü doğru, parse yanlış: 05.12 Mayıs'a dönüyor.

Dördüncüsü, datepicker'dan gelen Date'i olduğu gibi serialize etmek. UTC+3'te seçilen gün sunucuya bir önceki gün olarak gidiyor.

Beşincisi, çoklu seçimde required'a güvenmek. Boş array onu geçiyor; minLength(…, 1) kullan.

Küçük Bir Deneme

★☆☆ MAT_FORM_FIELD_DEFAULT_OPTIONS'a subscriptSizing: 'dynamic' ekle ve hint'i olmayan alanların altındaki boşluğun nasıl değiştiğini ekranda karşılaştır.

★★☆ Teslim tarihine hafta sonlarını kapatan bir [matDatepickerFilter] ekle. Klavyeyle bir Cumartesi yazınca Signal Forms'un hata listesinde hangi kind görünüyor? datepickerMessages'a mesajını ekle ve bunu bir testle doğrula.

★★★ Aynı form'un e-posta ve adet alanlarını Reactive Forms ile (FormGroup, formControlName) yeniden yaz ve ShowOnDirtyErrorStateMatcher'ı bu sefer orada dene. Boş e-posta submit sonrası görünüyor mu? Farkı isErrorState ile isSignalErrorState'in kaynağına bakarak açıkla.

Form hazır, sırada yönetim panelinin en çok kullanılan ekranı var: siparişler tablosu. Sonraki yazıda mat-table'ın column yapısını, MatSort ve MatPaginator'ı, 10.000 siparişi tarayıcıda sayfalamanın neden kötü fikir olduğunu ve sort, sayfa ve filtreyi URL'de tutan server-side sayfalamayı kuruyoruz.