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

Angular Material ve CDK'ya Giriş: ng add, Component Kataloğu, M3 Teması ve Material mı, CDK mı, Angular Aria mı?

Mert Özen 15 Kas 2026 14 dk 4 görüntülenme
Angular Material ve CDK'ya Giriş: ng add, Component Kataloğu, M3 Teması ve Material mı, CDK mı, Angular Aria mı?

Material component'lerini tek bir shared module'den değil, standalone class olarak import et: Bookstore'un yönetim panelinde 35 module'ü export eden shared dosya lazy chunk'ı 173 kB'tan 682 kB'a çıkardı ve derleyici uyarmadı. ng add'in neleri değiştirdiğini, marka renginden M3 teması ve density ayarını, toolbar ve sidenav ile responsive panel iskeletini, iç class override'ının neden kırılgan olduğunu ve icon font'unu 325 KB'tan 1,4 KB'a indirmeyi ölçüyoruz.

Sürüm notu: Bu yazıdaki örnekler Angular 22.2 (TypeScript 6.0), Angular Material ve CDK (Component Dev Kit; Angular ekibinin stilsiz davranış library'si) 22.2.7 ve Node.js 24 ile denenmiştir; tarayıcı davranışı Playwright 1.65 ile headless Chromium'da gözlenmiştir. Bookstore.Api ölçümlerde SQL Server yerine SQLite ile çalıştı.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore'a (bir online kitapçı) yönetim paneli ekleyeceğiz ve ekranları Angular Material ile kuracağız. Panel lazy loading ile ayrı bir chunk'ta duruyor. Üç soru: ng add @angular/material projede hangi dosyalara dokunur? Eski projelerde sık görülen "bütün Material module'lerini tek bir shared dosyadan export et" alışkanlığı, standalone component'lerin ve tree-shaking'in olduğu Angular 22'de bundle'a ne kadar ekler: birkaç kilobayt mı, birkaç yüz kilobayt mı? Üçüncüsü: iki menü ikonu için indirilen Material Symbols font dosyası kaç kilobayttır?

ng add Neleri Değiştiriyor?

Bookstore'un Angular istemcisi bookstore-web, katalog, kitap detayı ve sepet sayfalarından oluşan, Material kullanmayan küçük bir uygulama. Kuruluma tek komutla başladık. 22.2.7'deki ng add schematic'i yalnız bir şey soruyor: başlangıç renk paleti çifti (azure-blue, rose-red, magenta-violet, cyan-orange). Eski sürümlerdeki typography ve animasyon soruları artık yok:

ng add @angular/material
✔ Packages installed successfully.
CREATE src/material-theme.scss (1179 bytes)
UPDATE angular.json (2046 bytes)
UPDATE src/index.html (639 bytes)

Değişiklikler küçük. package.json'a @angular/material ve @angular/cdk geldi; @angular/animations gelmedi, çünkü Material 19.2'den beri animasyon module'üne bağımlı değil (components CHANGELOG'unda "drop dependency on animations module" satırı). Proje stil dosyası olarak styles.css kullandığı için schematic Sass dosyasına ekleme yapamadı; ayrı bir src/material-theme.scss oluşturup angular.json'da stil listesinin başına koydu. index.html'e de Roboto ile Material Symbols Outlined font'larının Google Fonts link'leri eklendi:

"styles": ["src/material-theme.scss", "src/styles.css"]

Material'ın package.json'ındaki peer dependency "@angular/cdk": "22.2.7", yani aralık değil tam sürüm. İki paket her zaman aynı sürümde güncellenmeli; ng update @angular/material CDK'yı da birlikte yükseltir. Provider tarafında hiçbir şey eklenmedi: app.config.ts'e dokunulmadı.

Katalog: Material, CDK ve Angular Aria

Paketin içine bakınca 35 component entry point'i görüyoruz (@angular/material/button, /table, /dialog…) ve bunlara ek olarak core. Dokümantasyon component'leri tek bir listede veriyor; günlük işte biz onları şöyle gruplayınca daha kolay buluyoruz: form kontrolleri (form-field, input, select, autocomplete, datepicker, timepicker, checkbox, radio, slide-toggle, slider, chips), navigation (toolbar, sidenav, menu, tabs, stepper), layout (list, card, divider, expansion, grid-list, tree), button ve göstergeler (button, button-toggle, icon, badge, progress-bar, progress-spinner, ripple), popup ve modal (dialog, bottom-sheet, snack-bar, tooltip) ve veri tablosu (table, sort, paginator). Serinin 02–04 yazıları bunların yönetim panelinde en çok kullanılanlarını tek tek kuruyor.

01-layers-tr.png

CDK, Material'ın altındaki katman: overlay, portal, drag-drop, virtual scroll, layout, table ve a11y gibi parçalar stilsiz davranış verir; Material'ın dialog'u da select'i de bunların üzerinde kurulu. Angular Aria (@angular/aria) ise üçüncü yol: combobox, tabs, listbox, menu ve tree gibi widget'ların klavye davranışını ve ARIA (Accessible Rich Internet Applications; screen reader'a role ve durum bildiren attribute'lar) attribute'larını hazır verir, görünümü tamamen senin CSS'ine bırakır. O da CDK'ya bağlı: peer dependency'si @angular/cdk 22.2.7. Karar sırası kısaca şöyle: native element yetiyorsa hiçbiri; Material Design görünümü işine geliyorsa Material; kendi tasarım sistemin varsa ve karmaşık bir widget lazımsa Angular Aria; kendi component'ini yazıyorsan CDK. Bu sıranın accessibility tarafını ve Angular Aria'nın 22.0.0'da stabil olduğunu Accessibility (a11y) serisinin "Angular'da Accessible Component'ler: Angular Aria, CDK a11y, Material ve Kendi Combobox'ını Yazmamak" yazısında ölçtük; burada tekrar etmiyoruz.

M3 Teması: Marka Rengi, Typography ve Density

ng add'in ürettiği tema hazır bir palet kullanıyor. Bookstore'un mevcut CSS'indeki marka rengi #1d4ed8; Material'ın palet schematic'i tek bir renkten M3 (Material Design 3; Google'ın tasarım sisteminin üçüncü sürümü) tonal paletlerini üretiyor:

ng generate @angular/material:theme-color --primary-color="#1d4ed8" --directory=src/
CREATE src/_theme-colors.scss (2516 bytes)
// Note: Color palettes are generated from primary: #1d4ed8
$_palettes: (
  primary: (
    0: #000000,
    10: #001551,
    20: #002682,
    // ...
    40: #2151da,
    // ...
    100: #ffffff,
  ),

Tema dosyası artık hazır palet yerine bu paletleri kullanıyor. Önemli değişiklik dosyanın sonunda: ng add body'ye Material'ın arka plan rengini, yazı rengini ve Roboto'yu yazıyordu. Bu kural bütün uygulamaya uygulandığı için müşterinin gördüğü katalog sayfasının fontu da Roboto'ya döndü. Material yalnız yönetim panelinde kullanılacağı için o üç satırı panelin kök class'ına taşıdık:

@use '@angular/material' as mat;
// Bookstore marka rengi #1d4ed8'den üretildi: ng generate @angular/material:theme-color
@use './theme-colors' as brand;

html {
  height: 100%;
  @include mat.theme(
    (
      color: (
        primary: brand.$primary-palette,
        tertiary: brand.$tertiary-palette,
      ),
      typography: Roboto,
      density: 0,
    )
  );
}

body {
  color-scheme: light;
  margin: 0;
  height: 100%;
}

// Material görünümü yalnız yönetim panelinde; katalog kendi font'unu ve arka planını korur
.admin-shell {
  background-color: var(--mat-sys-surface);
  color: var(--mat-sys-on-surface);
  font: var(--mat-sys-body-medium);
  // Yönetim ekranları yoğun liste ve tablo: bir density adımı = 4px daha az
  @include mat.theme((density: -1));
}

Tarayıcıda ölçtüğümüz değerler:

# .admin-shell içinde density: 0
--mat-sys-primary: light-dark(#2151da, #b7c4ff)  toolbar: 64px  nav item: 48px
# .admin-shell içinde density: -1
--mat-sys-primary: light-dark(#2151da, #b7c4ff)  toolbar: 60px  nav item: 44px

İki ayrıntı var. Birincisi, light temadaki primary renk #1d4ed8 değil #2151da: M3 paleti rengi tonlara bölüyor ve light tema primary için 40 tonunu kullanıyor. Logodaki renkle birebir aynı ton gerekiyorsa paleti elle düzeltmek gerekir; bunun Sass tarafını, light-dark() ile dark mode'u ve token'ları custom property'ye çevirmeyi Sass/SCSS serisinin "Sass ile Theming: Map'ler, CSS Custom Property Üretmek, Dark Mode ve Angular Material M3 Teması" yazısı anlatıyor. İkincisi, mat.theme'i bir selector içinde yalnız density ile çağırmak o bölgedeki component'leri sıklaştırıyor; tema dokümanına göre her adım boyutları 4px azaltıyor ve datepicker gibi popup'lar density'den etkilenmiyor. Doküman ayrıca 0'ın altındaki density'nin accessibility'yi azaltabileceğini hatırlatıyor; biz -1'de kaldık.

Yönetim Paneli İskeleti: Toolbar, Sidenav ve Nav List

Katalog, kitap ve sepet sayfaları kendi header'larıyla bir StoreLayout altına taşındı; yönetim paneli /admin altında lazy loading ile yükleniyor. Böylece Material kodu yalnız panele girildiğinde iniyor:

export const routes: Routes = [
  {
    path: '',
    component: StoreLayout,
    children: [
      { path: '', component: CatalogPage, title: 'Bookstore' },
      { path: 'books/:isbn', component: BookDetailPage, title: 'Book · Bookstore' },
      { path: 'cart', component: CartPage, title: 'Cart · Bookstore' },
    ],
  },
  // Yönetim paneli: kendi chunk'ı, uygulamada Angular Material kullanan tek bölüm
  { path: 'admin', loadChildren: () => import('./admin/admin.routes').then(m => m.adminRoutes) },
  { path: '**', redirectTo: '' },
];

Panelin iskeleti üç component: üstte mat-toolbar, solda mat-sidenav içinde bir mat-nav-list, sağda route içeriği. Material component'lerini module olarak değil, standalone class olarak import ediyoruz:

@Component({
  selector: 'app-admin-shell',
  imports: [
    MatToolbar, MatSidenavContainer, MatSidenav, MatSidenavContent, MatNavList, MatListItem, MatListItemIcon,
    MatListItemTitle, MatIcon, MatButton, MatIconButton, RouterOutlet, RouterLink, RouterLinkActive,
  ],
  templateUrl: './admin-shell.html',
  styleUrl: './admin-shell.scss',
})
export class AdminShell {
  // CDK Breakpoints.Handset: portrait'te 600px, landscape'te 960px'ten dar
  protected readonly handset = toSignal(
    inject(BreakpointObserver).observe(Breakpoints.Handset).pipe(map(state => state.matches)),
    { initialValue: false },
  );
  protected readonly mode = computed(() => (this.handset() ? 'over' : 'side'));

  protected readonly links = [
    { path: 'dashboard', icon: 'dashboard', label: 'Dashboard' },
  ];
}
<div class="admin-shell">
  <mat-toolbar>
    @if (handset()) {
      <button matIconButton type="button" aria-label="Open navigation" (click)="drawer.toggle()">
        <mat-icon>menu</mat-icon>
      </button>
    }
    <span class="title">Bookstore Admin</span>
    <span class="spacer"></span>
    <a matButton routerLink="/">Storefront</a>
  </mat-toolbar>

  <mat-sidenav-container class="container">
    <mat-sidenav #drawer [mode]="mode()" [opened]="!handset()">
      <mat-nav-list>
        @for (link of links; track link.path) {
          <a mat-list-item [routerLink]="link.path" routerLinkActive #active="routerLinkActive"
             [activated]="active.isActive" (click)="handset() && drawer.close()">
            <mat-icon matListItemIcon>{{ link.icon }}</mat-icon>
            <span matListItemTitle>{{ link.label }}</span>
          </a>
        }
      </mat-nav-list>
    </mat-sidenav>
    <mat-sidenav-content>
      <main class="content">
        <router-outlet />
      </main>
    </mat-sidenav-content>
  </mat-sidenav-container>
</div>

Button'larda 22.2'nin tercih ettiği matButton ve matIconButton selector'larını kullandık; eski mat-button ve mat-icon-button attribute'ları hâlâ selector listesinde. Dashboard sayfası bir mat-card içinde katalog özetini gösteriyor: httpResource ile /api/books?pageSize=1'den toplamı, /api/categories'ten kategori sayısını alıyor, yüklenirken bir mat-progress-bar çiziyor.

01-admin-shell-tr.png

Geniş ekranda sidenav side modunda içeriğin yanında açık duruyor; 360px genişlikte over moduna geçip kapanıyor ve toolbar'da menü butonu çıkıyor. Playwright ile ölçtüklerimiz:

desktop sidenav mode mat-drawer mat-sidenav mat-drawer-opened mat-drawer-side
mobile sidenav mode mat-drawer mat-sidenav mat-drawer-over
# menü butonuna tıkladıktan sonra
mobile open mat-drawer mat-sidenav mat-drawer-over mat-drawer-opened content inert: true

Son satır 22.0.0 ile gelen bir değişiklik: over modunda sidenav açıkken içerik inert oluyor, yani klavye ve screen reader arkadaki sayfaya geçemiyor. BreakpointObserver'ı burada yalnız modu seçmek için kullandık; CSS'in çözemediği bu tür durumları ve ne zaman gereksiz olduğunu serinin 07. yazısı ayrıntılı ele alıyor.

Bundle'a Ne Kadar Ekliyor?

Material'dan önceki ve sonraki production build'ler:

# önce: Material yok
main-BDBHRKZT.js    | main          | 264.83 kB |                73.11 kB
                    | Initial total | 265.76 kB |                74.05 kB

# sonra: Material'lı yönetim paneli
chunk-VK7IGDT4.js   | -             | 291.01 kB |                80.66 kB
styles-XPDJZGP6.css | styles        |  11.63 kB |                 2.00 kB
main-ZXWJ4VXZ.js    | main          |  10.29 kB |                 3.33 kB
                    | Initial total | 312.94 kB |                85.98 kB

Lazy chunk files    | Names         |  Raw size | Estimated transfer size
chunk-BPOV3ZKE.js   | admin-routes  | 173.01 kB |                29.47 kB

Material ve CDK kodu tamamen admin-routes chunk'ında (build stats'ına göre 137.1 kB Material, 27.0 kB CDK). Yine de katalog sayfasının indirdiği initial bundle 47 kB raw, yaklaşık 12 kB transfer büyüdü. Stil dosyası 0.9 kB'tan 11.6 kB'a çıktı, çünkü M3 temasının bütün --mat-sys-* custom property'leri html'de tanımlı; sıkıştırılınca bu yalnız 1 kB. Transfer artışının geri kalanı JS: Material'ın kullandığı @angular/core ve router parçaları, main ile lazy chunk'ın paylaştığı chunk'a taşındı. Admin route'unu geçici olarak kaldırınca main yine 265.11 kB'a döndü. Lazy loading Material'ı katalogdan uzak tutuyor ama sıfıra indirmiyor.

Şimdi tahminin ikinci sorusu. Eski projelerde sık görülen SharedMaterialModule'ü kurup 35 module'ün hepsini export ettik ve admin component'lerine yalnız onu verdik. Aynı ölçümü üç farklı import şekliyle tekrarladık:

// NgModule döneminden kalma "bütün Material tek dosyada" alışkanlığı
@NgModule({ imports: modules, exports: modules })
export class SharedMaterialModule {}
Admin component'lerinin import'uadmin-routes (raw)Tahmini transfer
Kullanılan standalone class'lar (MatToolbar, MatSidenav…)172.90 kB29.43 kB
Yalnız kullanılan 7 module (MatToolbarModule…)174.84 kB29.56 kB
35 module'ü export eden SharedMaterialModule681.79 kB108.31 kB
Aynı 35 module, imports'a verilen bir array olarak681.65 kB108.45 kB

Cevap birkaç yüz kilobayt: chunk dört katına çıktı. Build stats'ında Material kodu 137.1 kB'tan 511.0 kB'a, CDK kodu 27.0 kB'tan 116.3 kB'a çıktı ve panelde hiç kullanmadığımız @angular/forms'tan 33.0 kB geldi. Module'ü bir NgModule'e sarmakla array'e koymak arasında fark yok; fark yaratan, kullanılmayan module'lerin imports'a girmesi. Derleyicinin bu konudaki davranışı da farklı. Kullanılmayan bir standalone class için uyarı veriyor ve onu bundle'a koymuyor; kullanılmayan bir module için hiçbir şey söylemiyor:

# imports'a kullanılmayan MatSelect eklenince
▲ [WARNING] NG8113: MatSelect is not used within the template of DashboardPage [plugin angular-compiler]
chunk-BPOV3ZKE.js   | admin-routes  | 173.01 kB |                29.47 kB

# imports'a kullanılmayan MatSelectModule eklenince: uyarı yok
chunk-RO5TITOV.js   | admin-routes  | 291.18 kB |                48.32 kB

Tek bir unutulmuş MatSelectModule sessizce 118 kB ekledi. Standalone class import'u hem daha küçük hem de derleyicinin seni uyarabildiği tek yol.

Stili Değiştirmek: İç Class'lar mı, Override API'si mi?

Nav list'teki yazıları kalınlaştırmak istedik. DevTools'ta element'i seçince .mdc-list-item__primary-text class'ını görüyorsun ve onu override etmek cazip geliyor. Üç yolu denedik ve hesaplanan font-weight'i ölçtük:

// 1) Component stilinde iç class
.mdc-list-item__primary-text {
  font-weight: 700;
}

// 2) Aynısı ::ng-deep ile
::ng-deep .mdc-list-item__primary-text {
  font-weight: 700;
}

// 3) Material'ın override API'si
mat-nav-list {
  @include mat.list-overrides((
    list-item-label-text-weight: 700,
  ));
}
base            font-weight: 400
internal-class  font-weight: 700
ng-deep         font-weight: 400
list-overrides  font-weight: 700

Sonuç kafa karıştırıcı ve asıl ders de bu. Birinci yol çalıştı, çünkü matListItemTitle class'ı bizim template'imizdeki span'e ekliyor; encapsulation'ın eklediği attribute selector'ın özgüllüğü Material'ın kuralını geçti. İkinci yol çalışmadı: ::ng-deep attribute'u kaldırınca bizim kuralımızla Material'ın kuralı aynı özgüllüğe düştü ve sayfaya sonra eklenen Material stili kazandı. Yani iç class override'ı, DOM (Document Object Model; sayfanın tarayıcıdaki element ağacı) yapısına, encapsulation'a ve stillerin sayfaya eklenme sırasına bağlı; bunların hepsi bir sonraki Material sürümünde değişebilir. Tema dokümanı da component'lerin DOM yapısını ve class'larını private sayıyor ve override API'sinin dışındaki CSS değişikliklerini desteklemiyor. Üçüncü yol bir token'ı (--mat-list-list-item-label-text-weight) değiştiriyor; Material'ın kendi kuralı o token'ı okuyor. Toolbar'ın arka planını da aynı yolla değiştirdik:

// .mat-toolbar / .mat-mdc-* class'larını ezmek yerine public theming API'si
mat-toolbar {
  @include mat.toolbar-overrides((
    container-background-color: var(--mat-sys-surface-container),
  ));
}

Token override'ları, system token'ları ve eski projelerden kalan .mat-* override'larının neden sessizce etkisiz kaldığı serinin son yazısının konusu.

Test: Harness ile Sidenav Modu

Bookstore istemcisinde Vitest ile birim testleri var; panelin iki davranışını da teste bağladık. BreakpointObserver'ı sahte bir değerle değiştirip sidenav'ı Material'ın test harness'ıyla sorguluyoruz, böylece test sidenav'ın iç DOM'una bağlı kalmıyor:

async function render(handset: boolean) {
  const state: BreakpointState = { matches: handset, breakpoints: {} };
  TestBed.configureTestingModule({
    imports: [AdminShell],
    providers: [provideRouter([]), { provide: BreakpointObserver, useValue: { observe: () => of(state) } }],
  });
  const fixture = TestBed.createComponent(AdminShell);
  const sidenav = await TestbedHarnessEnvironment.loader(fixture).getHarness(MatSidenavHarness);
  return { element: fixture.nativeElement as HTMLElement, sidenav };
}

it('turns the navigation into a closed overlay with a menu button on handsets', async () => {
  const { element, sidenav } = await render(true);
  expect(await sidenav.getMode()).toBe('over');
  expect(await sidenav.isOpen()).toBe(false);
  expect(element.querySelector('button[aria-label="Open navigation"]')).not.toBeNull();
});
ng test --watch=false
 ✓ src/app/store-layout.spec.ts > StoreLayout > shows the brand and an empty cart
 ✓ src/app/admin/dashboard-page.spec.ts > DashboardPage > shows the catalog totals from the API
 ✓ src/app/admin/admin-shell.spec.ts > AdminShell > keeps the navigation open beside the content on wide screens
 ✓ src/app/admin/admin-shell.spec.ts > AdminShell > turns the navigation into a closed overlay with a menu button on handsets
 Test Files  3 passed (3)
      Tests  4 passed (4)

Dashboard testinde küçük bir tuzak çıktı: httpResource isteği bekleyen bir task olduğu için ilk fixture.whenStable() isteğin bitmesini bekliyor, istek de biz flush etmeden bitmiyor; test 5 saniyede timeout'a düştü. İlk adımda fixture.detectChanges() çağırıp isteği başlatmak, sonra flush etmek sorunu çözdü. Harness'ların kendisi ve kendi harness'ını yazmak Frontend Testleri serisinin "Component Harness'ları: CDK Harness Kullanmak, Kendi Harness'ını Yazmak ve Testleri Markup'tan Bağımsızlaştırmak" yazısında.

Üretimde Dikkat

Icon font'unu küçült. Tahminin üçüncü sorusu: ng add'in eklediği link bütün Material Symbols setini indiriyor. Panelde iki ikon (menu ve dashboard) varken ölçtüğümüz woff2 dosyası 325,060 byte'tı. Google Fonts'un icon_names parametresiyle yalnız kullanılan ikonları istedik ve dosya 1,392 byte'a indi:

<link
  href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined&amp;icon_names=dashboard,menu&amp;display=block"
  rel="stylesheet"
/>
# önce: bütün Material Symbols seti
fonts.gstatic.com/s/materialsymbolsoutlined  font/woff2  325060
# sonra: icon_names=dashboard,menu
fonts.gstatic.com/l/font  font/woff2  1392

Google'ın dokümanına göre liste alfabetik sırada olmalı. Bedeli, her yeni ikonda bu listeyi güncellemek; unutursan ikonun yerinde ligature metni ("delete" gibi) görünür. Uygulama Google Fonts'a hiç gitmemeliyse font'u kendi sunucundan servis etmek ya da MatIconRegistry ile SVG (Scalable Vector Graphics; vektörel görsel formatı) ikon kullanmak diğer seçenek.

Production build internete çıkıyor. Angular CLI'ın font optimizasyonu Google Fonts CSS'ini build sırasında indirip index.html'e gömüyor; build'imizde iki @font-face bloğu ve tema değişkenleri index.html'e yazıldı (24,920 byte). İnternete kapalı bir CI ortamında build'in bu adımı başarısız olur ya da optimizasyonu kapatman gerekir.

Tema kapsamını overlay'lere göre seç. Biz mat.theme'i html'de bıraktık ve yalnız font ile arka planı .admin-shell'e taşıdık. Temayı tamamen .admin-shell içine almak cazip ama dialog, menu ve select paneli gibi overlay'ler bu element'in dışında render ediliyor; o zaman token'ları göremezler. Bunu dialog yazısında ölçüyoruz.

Material ile CDK'yı aynı anda güncelle. Peer dependency tam sürüm istediği için npm install @angular/material@latest tek başına çakışma üretir; ng update @angular/material ikisini birlikte ve migration'larıyla yükseltir.

Sık Düşülen Tuzaklar

Birincisi, bütün Material module'lerini tek bir shared dosyadan import etmek. Ölçtüğümüz panelde chunk 173 kB'tan 682 kB'a çıktı ve derleyici tek satır uyarı vermedi. Standalone class'ları import et.

İkincisi, Material'ın iç class'larını (.mdc-*, .mat-mdc-*) doğrudan override etmek. Aynı kural encapsulation ile çalışıp ::ng-deep ile çalışmadı; bir sonraki sürümde ikisi de değişebilir. Override mixin'lerini ve token'ları kullan.

Üçüncüsü, ng add'in global stillerini olduğu gibi bırakmak. body'ye yazılan font, Material kullanmayan sayfaları da değiştirdi.

Dördüncüsü, density'yi her yerde düşürmek. Formlarda ve dokunmatik ekranlarda küçülen hedefler kullanımı zorlaştırır; yoğunluğu yalnız gerçekten sıkışan bölgede düşür.

Küçük Bir Deneme

★☆☆ material-theme.scss'te .admin-shell içindeki density'yi -3 yap ve toolbar ile nav list yüksekliklerini DevTools'ta ölç. Her adımda 4px azalma kuralı tutuyor mu, hangi değerde durdu?

★★☆ DashboardPage'in imports'una kullanmadığın MatDatepickerModule'ü ekle, ng build al ve admin-routes chunk'ının ne kadar büyüdüğüne bak. Sonra aynı denemeyi MatDatepicker class'ıyla yap; derleyici hangisinde uyardı?

★★★ Panele ikinci bir link ekle (ör. inventory ikonuyla "Stock") ve Material Symbols link'indeki icon_names listesini güncellemeyi unut. Ekranda ne görünüyor? Listeyi build sırasında template'lerdeki mat-icon'lardan üreten küçük bir script yaz.

Panelin iskeleti hazır. Sonraki yazıda içine ilk gerçek ekranı koyuyoruz: kurumsal sipariş formu. mat-form-field'in appearance'ları, mat-error'un ne zaman göründüğü, sunucudan arama yapan mat-autocomplete, compareWith'li mat-select ve Türkçe tarih, Pazartesi ile başlayan hafta ve saat dilimi kaymasıyla mat-datepicker.