Paylaştığınız müfredat belgesini satır satır inceleyerek, belirttiğiniz kurallar doğrultusunda temizledim. İçerisinde dakika, saniye, "Önizleme" ve "ders" geçen tüm ifadeleri ayıkladım.

İlk olarak, bu eğitimin temel amacı olan "Öğrenecekleriniz" bölümünün ne anlama geldiğini netleştirelim. Ardından tüm konu başlıklarını büyük bir mimari plan dahilinde tasarlayıp, her birini örnekler ve uygulamalarla detaylıca açıklayalım.


🎨 "Öğrenecekleriniz" Bölümünün Açıklaması

Bu bölüm, eğitim bittiğinde cebinizde olacak profesyonel yetenekleri gösterir:


Mimarinin Tasarımı ve Konuların Detaylı Açıklaması

Bir web geliştiricisi olarak Angular dünyasına adım atarken izleyeceğimiz yol haritası temelden ileri seviyeye doğru 5 ana sütun üzerine kurulmuştur:

  1. Giriş ve Şablon (Template) Mimarisi: Çevre kurulumu ve ekranda veri gösterme.
  2. Bileşen (Component) Dünyası ve Veri Akışı: Arayüzü parçalara bölme ve konuşturma.
  3. Veri Dönüşümü ve Dinamik Yapılar (Pipes & Directives): Veriyi şekillendirme ve HTML'e hükmetme.
  4. Büyük Ölçekli Mimari (Modules, Routing, Lazy Loading): Sayfalar arası geçiş ve performans.
  5. Gelişmiş Seviye (TypeScript, HTTP, Güvenlik, RxJS ve Formlar): Arka plan süreçleri, sunucu bağlantısı ve veri yönetimi.

Şimdi tüm bu konuları pratik örnekler ve uygulamalarla tek tek ele alalım.


1. Giriş, Çevre Kurulumu ve Şablon (Template) Sözdizimi

Angular ile kod yazmaya başlamadan önce bilgisayarımızı bu sisteme hazırlamamız gerekir. Ardından Angular'ın kendine has HTML yazım dilini öğreniriz. [4, 6]

Çevre Kurulumu (Node.js, Angular CLI, VS Code)

Angular projelerini çalıştırmak için arka planda Node.js motoruna ihtiyaç duyarız. Kodlarımızı yazmak için Visual Studio Code editörünü, Angular komutlarını hızlıca çalıştırmak için ise Angular CLI (Komut Satırı Aracı) sistemini kurarız. [4, 6]

Şablon Sözdizimi (Template Syntax) ve Bağlamalar (Bindings)

Angular'da HTML kodları ile TypeScript (kod mantığı) kodları birbiriyle konuşur. [7]


2. Bileşen (Component) Yapısı ve Geliştirilmesi

Angular projeleri tek bir büyük parça yerine, küçük küçük kutucuklardan oluşur. Her kutucuğa Component (Bileşen) denir. [4]

Bileşen Oluşturma ve Haberleşme

Bir web sitesindeki menü, yan bar ve logo alanlarının her biri ayrı birer bileşendir. Bu bileşenler anne-çocuk (Parent-Child) ilişkisi kurarak birbirine veri gönderir. [4]


3. Borular (Pipes) ve Yönergeler (Directives)

Verileri ekranda göstermeden önce süslemek veya HTML elementlerinin davranışlarını değiştirmek için bu yapıları kullanırız. [4, 7]

Pipes (Veri Şekillendiriciler)

Arka plandaki ham veriyi bozmadan, sadece ekrandaki görünüşünü değiştiren yapılardır. [4, 7]

Directives (HTML'e Hükmedenler)

HTML etiketlerine yeni yetenekler kazandırır. [7, 10]


4. Modül Sistemi, Yönlendirme (Routing) ve Tembel Yükleme (Lazy Loading)

Uygulamamız büyüdükçe sayfaların hızlı açılması ve kodların düzenli kalması için bu mimari araçları kullanırız. [4, 5]

Modül Sistemi (Modules)

İlgili bileşenleri ve kodları tek bir paket altında toplama işlemidir. Örneğin, kargo işlemleri ile ilgili tüm ekranları KargoModule adında bir pakete koyarız. [4]

Yönlendirme (Routing)

Kullanıcı menüden "Hakkımızda" linkine tıkladığında, sayfa yenilenmeden sadece ekranın ortasındaki alanın değişmesini sağlar. <router-outlet> etiketi, yeni sayfanın nereye açılacağını belirler. [2, 11, 12]

Tembel Yükleme (Lazy Loading)

Kullanıcı siteye ilk girdiğinde sadece ana sayfayı indirir. "Ayarlar" sayfasına tıklamadığı sürece o sayfanın kodlarını internetten yüklemez. Bu sayede site ilk açılışta çok hızlı olur. [2, 5]


5. TypeScript Derinlikleri, HTTP İstekleri, Güvenlik ve Formlar

Angular'ın arkasındaki gerçek güç, güvenli kod yazmayı sağlayan TypeScript dili ve sunucu ile olan iletişimidir. [4, 5]

TypeScript Temelleri (Gelişmiş Seviye)

TypeScript, JavaScript'in üzerine kurulu ve hata yapmanızı engelleyen bir korumadır. Değişkenlerin türlerini (yazı mı, sayı mı) önceden belirleriz. [5, 13, 14]

HTTP İstekleri (Veri Çekme) ve Güvenlik

Uygulamamızın internetteki bir sunucudan (API) canlı verileri çekmesini sağlar. HttpClient servisi yardımıyla GET, POST gibi istekler atılır. Güvenlik tarafında ise Angular, dışarıdan gelebilecek kötü amaçlı kod enjeksiyonlarını (XSS Saldırıları) otomatik olarak temizler (escaping). [3, 5, 12, 15]

RxJS (Reaktif Programlama)

Verileri anlık bir akış (Stream) olarak ele alır. Bir arama kutusuna harf yazıldığında, her harfte sunucuya istek atmak yerine kullanıcının durmasını bekleyen akıllı filtreleme operatörlerine (Örn: switchMap, debounceTime) sahiptir.

Form Yönetimi (Reactive & Template Forms)

Kullanıcıdan bilgi toplarken iki yol izlenir: [5]

 

 

 

Harika bir seçim! Angular dünyasına adım atmak için önce bilgisayarımızı hazırlayalım, ardından en önemli konulardan biri olan bileşenler arası haberleşmeyi pratik bir kod örneğiyle çözelim.


🛠️ Bölüm 1: Çevre Kurulumu ve İlk Projenin Oluşturulması

Angular projelerini bilgisayarınızda derlemek ve çalıştırmak için sırasıyla şu 3 adımı yapmanız gerekir:

Adım 1: Node.js Kurulumu

Node.js, yazdığımız Angular kodlarının bilgisayarımızda çalışmasını sağlayan arka plan motorudur.

  1. Tarayıcınızdan nodejs.org adresine gidin.
  2. LTS (Önerilen) sürümünü bilgisayarınıza indirin ve kurun. (Kurulum sırasında sadece "İleri/Next" diyerek bitirebilirsiniz).

Adım 2: Angular CLI (Komut Satırı) Kurulumu

Angular CLI, bizim yerimize otomatik olarak projeler, sayfalar ve bileşenler oluşturan sihirli bir yardımcı araçtır.

  1. Bilgisayarınızın arama çubuğuna Terminal (Mac/Linux için) veya Komut İstemi / CMD (Windows için) yazıp açın.
  2. Şu komutu yazıp Enter tuşuna basın:

3.  npm install -g @angular/cli

  1. Kurulum bitince terminale ng v yazarak Angular sürümünüzü kontrol edebilirsiniz.

Adım 3: İlk Projeyi Oluşturma ve Çalıştırma

  1. Terminalde projenizi hangi klasöre kurmak istiyorsanız oraya gidin (Örn: cd Desktop).
  2. Şu komutla sıfırdan bir proje oluşturun:

3.  ng new ilk-projem

(Sistem size bazı sorular sorabilir; yönlendirmeler için yön tuşlarıyla "CSS" seçip Enter diyebilirsiniz).

  1. Proje klasörünün içine girin:

5.  cd ilk-projem

  1. Projenizi ayağa kaldırın:

7.  ng serve

  1. Tarayıcınızı açıp http://localhost:4200 adresine gidin. Tebrikler, ilk Angular uygulamanız çalışıyor!

🔄 Bölüm 2: Bileşenler Arası Veri Transferi (@Input, @Output)

Şimdi, projemizin içinde bir Anne (Parent) bileşen ve bir Çocuk (Child) bileşen olduğunu hayal edelim.

1. Çocuk Bileşen (Child Component)

Terminalde ng g c urun-kartı komutuyla bu bileşeni oluşturabiliriz. Kodları şu şekilde yazılır:

urun-karti.component.ts (Mantık Kısmı)

import { Component, Input, Output, EventEmitter } from '@angular/core';

 

@Component({

  selector: 'app-urun-karti',

  templateUrl: './urun-karti.component.html'

})

export class UrunKartiComponent {

  // @Input ile dışarıdan (anne bileşenden) gelecek veriyi kabul ediyoruz

  @Input() urunAdi: string = '';

 

  // @Output ile anne bileşene göndereceğimiz sinyali (etkinliği) tanımlıyoruz

  @Output() satinAlindi = new EventEmitter<string>();

 

  butonTiklandi() {

    // Butona basılınca annemize ürünün adını sinyal olarak fırlatıyoruz

    this.satinAlindi.emit(this.urunAdi);

  }

}

urun-karti.component.html (Arayüz Kısmı)

<div style="border: 1px solid gray; padding: 15px; margin: 10px;">

  <h3>Ürün: {{ urunAdi }}</h3>

  <!-- Butona tıklandığında kendi içindeki fonksiyonu tetikler -->

  <button (click)="butonTiklandi()">Satın Al</button>

</div>


2. Anne Bileşen (Parent Component)

Bu bileşen, yukarıdaki çocuk bileşeni kendi içinde barındırır ve onu yönetir. Genellikle ana sayfamız olan app.component yapısıdır.

app.component.ts (Mantık Kısmı)

import { Component } from '@angular/core';

 

@Component({

  selector: 'app-root',

  templateUrl: './app.component.html'

})

export class AppComponent {

  // Çocuğa göndereceğimiz veri

  gonderilecekUrun = 'Kablosuz Kulaklık';

 

  // Çocuktan sinyal geldiğinde çalışacak fonksiyon

  bildirimiAl(gelenUrun: string) {

    alert(gelenUrun + " başarıyla satın alındı! (Anne bileşen bu sinyali yakaladı)");

  }

}

app.component.html (Arayüz Kısmı)

<div style="padding: 20px;">

  <h2>Alışveriş Sayfası (Anne Bileşen)</h2>

 

  <!-- Çocuk bileşeni buraya çağırıyoruz -->

  <!-- [urunAdi] ile veriyi içeri gönderiyoruz -->

  <!-- (satinAlindi) ile içeriden gelen sinyali dinliyoruz -->

  <app-urun-karti

    [urunAdi]="gonderilecekUrun"

    (satinAlindi)="bildirimiAl($event)">

  </app-urun-karti>

</div>

🧠 Nasıl Çalıştı?

  1. Anne bileşendeki gonderilecekUrun değişkeninin içindeki yazı, çocuk bileşendeki [urunAdi] kutusuna aktarıldı.
  2. Ekranda "Ürün: Kablosuz Kulaklık" yazısı belirdi.
  3. Kullanıcı "Satın Al" butonuna bastığında, çocuk bileşen satinAlindi.emit() komutuyla yukarıya bir çığlık (sinyal) attı.
  4. Anne bileşen bu sinyali (satinAlindi)="bildirimiAl($event)" satırı sayesinde kulağıyla duydu ve ekrana uyarı mesajını (alert) çıkarttı.

 

Bu kapsamlı Angular 22 eğitim müfredatını adım adım inceliyor, belirlediğiniz kurallar doğrultusunda tüm karmaşık süreleri ve gereksiz ifadeleri temizleyerek baştan sona tasarlıyorum. Dosyada yer alan tüm ana bölümleri, teorik temelleri, kod örnekleri ve pratik uygulamalarla tamamen detaylandırılmış bir rehbere dönüştürüyorum.


"Öğrenecekleriniz" Paragrafının Açıklaması

Bu kurs, katılımcıları Angular dünyasında sıfırdan alıp profesyonel bir seviyeye taşımayı hedefler. Temel olarak şu yetkinlikleri kazandırır:


MODÜL 1: Angular Nedir ve İlk Adımlar

1. Framework (Çatı) ve Library (Kütüphane) Farkı

2. Proje Dosya Yapısı ve Kurulum

Angular projelerini yönetmek için Angular CLI adı verilen komut satırı aracı kullanılır. Yeni bir proje oluştururken şu adımlar izlenir:

# Angular CLI aracını bilgisayara kurma

npm install -g @angular/cli

 

# Yeni bir proje oluşturma

ng new my-first-app

 

# Geliştirme sunucusunu başlatma

ng serve

ng new komutu çalıştırıldığında Angular bizim için büyük bir dosya duvarı oluşturur:


MODÜL 2: Detaylı TypeScript Eğitimi

TypeScript, JavaScript dilinin üzerine inşa edilmiş, değişkenlerin türlerini net bir şekilde belirtmemizi sağlayan güvenli bir dildir. Angular, hataları daha kod yazarken yakalayabilmek için bu dili zorunlu tutar.

1. Temel Türler ve Gelişmiş Tipler

Verilerin türlerini belirterek kodun patlamasını önleriz.

// Temel Veri Tipleri

let isim: string = "Ahmet";

let yas: number = 26;

let aktifMi: boolean = true;

 

// Dangerous Any ve Safe Unknown Farkı

let rastgele: any = "Selam";

rastgele.OlmayanMetod(); // Hata vermez ama çalışma anında tarayıcı patlar!

 

let guvenli: unknown = "Merhaba";

// guvenli.OlmayanMetod(); // TypeScript buna hemen izin vermez, önce tür kontrolü ister!

 

// Union (Birlik) Tipleri ve Null/Undefined Kontrolü

let kullaniciId: string | number;

kullaniciId = "USR-123";

kullaniciId = 456;

2. Interface (Arayüz) ve Type Aliases

Objelerin şekillerini ve taşıyacakları özellikleri tanımlamak için kullanılırlar.

// Interface Tanımlama ve Genişletme

interface Kullanici {

  readonly id: number; // Değiştirilemez

  isim: string;

  eposta?: string; // Zorunlu değil (Optional)

}

 

interface Personel extends Kullanici {

  maas: number;

}

 

// Type Aliases ve Intersections (Kesişim)

type Durum = "beklemede" | "onaylandi" | "reddedildi"; // Literal Type

type Skor = number;

 

let mevcutDurum: Durum = "onaylandi"; // Sadece belirlenen 3 kelimeden birini alabilir


MODÜL 3: Standalone Components (Bağımsız Bileşenler)

Angular 22 sürümünde eski modül yapısı (NgModule) yerine tamamen Standalone Bileşenler varsayılan olarak kullanılır. Bileşenler web sayfasının bağımsız parçacıklarıdır. [1]

1. Bileşen Oluşturma ve Görünüm Kapsülleme (View Encapsulation)

Bileşenler HTML, CSS ve TypeScript kodlarının birleşimiyle oluşur.

# CLI ile yeni bir bağımsız bileşen üretme

ng generate component components/user-card

import { Component, ViewEncapsulation } from '@angular/core';

 

@Component({

  selector: 'app-user-card',

  standalone: true, // Bağımsız bileşen olduğunu belirtir

  imports: [], // Kullanacağı diğer bileşenler buraya eklenir

  templateUrl: './user-card.component.html',

  styleUrls: ['./user-card.component.css'],

  encapsulation: ViewEncapsulation.Emulated // CSS tarzlarının dışarı sızmasını engeller

})

export class UserCardComponent {

  // Bileşen mantığı buraya yazılır

}

2. Bileşenler Arası İletişim (@Input, @Output)

Bileşenler birbirlerine veri aktarabilir ve haberleşebilirler.

// child.component.ts (Çocuk Bileşen)

import { Component, Input } from '@angular/core';

 

@Component({

  selector: 'app-child',

  standalone: true,

  template: `<p>Gelen Değer: {{ veri }}</p>`

})

export class ChildComponent {

  @Input() veri: string = '';

}

// child.component.ts (Çocuk Bileşen)

import { Component, Output, EventEmitter } from '@angular/core';

 

@Component({

  selector: 'app-child',

  standalone: true,

  template: `<button (click)="tetikle()">Tıkla ve Gönder</button>`

})

export class ChildComponent {

  @Output() tiklamaEtkinligi = new EventEmitter<string>();

 

  tetikle() {

    this.tiklamaEtkinligi.emit('Çocuk bileşenden selamlar!');

  }

}

3. İçerik Yansıtma (Content Projection - ng-content)

Bir bileşenin içine dışarıdan dinamik HTML tasarımları göndermek istediğimizde slot mantığını kullanırız.

<!-- Reusable Card Bileşeni İç Tasarımı (card.component.html) -->

<div class="card">

  <div class="card-header">

    <ng-content select="[baslik]"></ng-content>

  </div>

  <div class="card-body">

    <ng-content select="[icerik]"></ng-content>

  </div>

</div>

<!-- Bu kartı başka bir yerde kullanırken -->

<app-card>

  <h2 baslik>Profil Kartı</h2>

  <p icerik>Bu alan dinamik olarak kartın gövdesine yerleşecektir.</p>

</app-card>


MODÜL 4: Şablonlar ve Veri Bağlama (Templates & Data Binding)

Angular, TypeScript sınıfındaki verileri HTML ekranına dinamik olarak bağlamak için güçlü araçlar sunar.

1. Veri Bağlama Yöntemleri

Bağlama Yöntemi

Sözdizimi (Syntax)

Açıklama

Interpolation

{{ veri }}

TypeScript değişkenini düz metin olarak ekrana basar.

Property Binding

[src]="resimUrl"

HTML etiketinin özelliklerine dinamik değer atar.

Event Binding

(click)="islem()"

Kullanıcı etkileşimlerini (tıklama, yazma vb.) dinler.

Two-Way Binding

[(ngModel)]="isim"

Hem ekrandan veriyi okur hem de ekrandaki değişimi koda yazar.

<!-- Veri Bağlama Uygulaması -->

<div>

  <h3>Hoş geldin, {{ kullaniciAd }}</h3>

  <img [src]="profilResmi" alt="Kullanıcı Resmi">

 

  <input [(ngModel)]="kullaniciAd" placeholder="Adını değiştir">

  <button (click)="sayfayiTemizle()">Temizle</button>

</div>

2. Yeni Kontrol Akışı (Control Flow)

Angular 22 ile gelen yeni @if ve @for yapıları sayesinde artık eski şablon direktiflerine ihtiyaç duymadan temiz döngüler ve şartlar yazabiliriz. [3]

<!-- @if, @else if, @else Kullanımı -->

@if (rol === 'admin') {

  <p>Tüm yetkilere sahipsiniz.</p>

} @else if (rol === 'editor') {

  <p>İçerikleri düzenleyebilirsiniz.</p>

} @else {

  <p>Sadece okuma yetkiniz var.</p>

}

 

<!-- @for Döngüsü ve @empty Bloğu -->

<ul>

  @for (urun of urunler; track urun.id; let i = $index) {

    <li>{{ i + 1 }} - {{ urun.ad }} (Stok: {{ urun.stok }})</li>

  } @empty {

    <li>Listelenecek ürün bulunamadı.</li>

  }

</ul>

Not: @for yapısında track kullanımı performans açısından zorunludur. Listenin hangi benzersiz alana göre güncelleneceğini belirler. [3]


MODÜL 5: Direktifler (Directives)

Direktifler, HTML elementlerinin davranışlarını değiştiren veya onlara yeni özellikler ekleyen yapılardır.

1. Sınıf ve Stil Bağlama Örnekleri

<!-- Şartlı Sınıf Ekleme -->

<div [class.aktif-kutu]="durum === 'aktif'" [class.pasif-kutu]="durum === 'pasif'">

  Sınıf Bağlama Kutusu

</div>

 

<!-- Dinamik Stil Verme -->

<button [style.background-color]="hataVar Mi ? 'red' : 'green'" [style.width.px]="butonGenisligi">

  Gönder

</button>

2. Özel Nitelik Direktifi Yazma (Custom Attribute Directive)

Bir elementin üzerine gelindiğinde arka plan rengini değiştiren bir direktif yapalım:

import { Directive, ElementRef, HostListener, Input } from '@angular/core';

 

@Directive({

  selector: '[appHighlight]',

  standalone: true

})

export class HighlightDirective {

  @Input() appHighlight = 'yellow'; // Dışarıdan renk alınabilir

 

  constructor(private el: ElementRef) {}

 

  @HostListener('mouseenter') onMouseEnter() {

    this.renk Ver(this.appHighlight);

  }

 

  @HostListener('mouseleave') onMouseLeave() {

    this.renk Ver(null);

  }

 

  private renkVer(renk: string | null) {

    this.el.nativeElement.style.backgroundColor = renk;

  }

}

<!-- HTML üzerinde kullanımı -->

<p [appHighlight]="'cyan'">Bu metnin üzerine gelirseniz rengi değişir.</p>


MODÜL 6: Borular (Pipes) - Veri Dönüştürme

Borular, ham veriyi HTML şablonunda göstermeden hemen önce biçimlendirmek için kullanılır. Bileşen içindeki orijinal veriyi değiştirmezler.

1. Hazır Borular ve Parametre Kullanımı

<!-- Metin Dönüşümleri -->

<p>{{ 'anKara' | uppercase }}</p> <!-- Çıktı: ANKARA -->

<p>{{ 'açık kaynak kod' | titlecase }}</p> <!-- Çıktı: Açık Kaynak Kod -->

 

<!-- Sayı ve Para Birimi Biçimlendirme -->

<p>{{ 1250.5 | currency:'EUR':'symbol':'1.2-2' }}</p> <!-- Çıktı: €1.250,50 -->

<p>{{ 0.75 | percent }}</p> <!-- Çıktı: %75 -->

 

<!-- Tarih Biçimlendirme -->

<p>{{ bugun | date:'fullDate' }}</p>

2. Özel Boru Yazma (Custom Pipe)

Metinleri belirli bir uzunluktan sonra kesip sonuna "..." ekleyen bir boru tasarlayalım:

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

 

@Pipe({

  name: 'ozetle',

  standalone: true,

  pure: true // Performans için sadece girdi değiştiğinde çalışır

})

export class OzetlePipe implements PipeTransform {

  transform(deger: string, limit: number = 10): string {

    if (!deger) return '';

    return deger.length > limit ? deger.substring(0, limit) + '...' : deger;

  }

}

<!-- Kullanımı -->

<p>{{ 'Bu çok uzun bir paragraf içeren metindir' | ozetle:15 }}</p>

<!-- Çıktı: Bu çok uzun bir... -->


MODÜL 7: Angular Servisleri ve Bağımlılık Enjeksiyonu (Dependency Injection)

Servisler, merkezi veri yönetimini sağlamak ve bileşenler arasındaki ortak iş mantığını yürütmek için kullanılır.

1. Servis Oluşturma ve @Service Dekoratörü

Angular 22 ile birlikte, global servis tanımlamaları için daha ergonomik olan yeni @Service dekoratörü getirilmiştir. [2, 4]

import { Service } from '@angular/core';

 

@Service() // Angular 22 ile gelen yeni kısayol tanımlama

export class VeriServisi {

  private ogrenciler = ['Ali', 'Ayşe', 'Fatma'];

 

  getOgrenciler() {

    return this.ogrenciler;

  }

 

  ogrenciEkle(yeniOgrenci: string) {

    this.ogrenciler.push(yeniOgrenci);

  }

}

2. Servisin Bileşene Enjekte Edilmesi (inject)

Modern Angular mimarisinde servisleri çağırmak için inject fonksiyonu tercih edilir. [2, 4]

import { Component, inject } from '@angular/core';

import { VeriServisi } from './veri.servisi';

 

@Component({

  selector: 'app-ogrenci-liste',

  standalone: true,

  template: `

    <button (click)="yeniEkle()">Öğrenci Ekle</button>

    <ul>

      @for (o of liste; track o) { <li>{{ o }}</li> }

    </ul>

  `

})

export class OgrenciListeComponent {

  private veriServisi = inject(VeriServisi); // Servisin enjekte edilmesi

  liste = this.veriServisi.getOgrenciler();

 

  yeniEkle() {

    this.veriServisi.ogrenciEkle('Mehmet');

  }

}


MODÜL 8: Angular Form Yönetimi

Angular'da iki farklı form yönetim modeli bulunur: Şablon Güdümlü (Template-Driven) ve Reaktif (Reactive) formlar. [5]

1. Şablon Güdümlü Formlar (Template-Driven)

Form tasarımı ve tüm doğrulama (validation) kuralları HTML tarafında kurgulanır. Küçük ve basit formlar için uygundur. [5, 6, 7]

<!-- template-form.component.html -->

<form #kullaniciFormu="ngForm" (ngSubmit)="kaydet(kullaniciFormu.value)">

  <div>

    <label>Kullanıcı Adı:</label>

    <input type="text" name="username" ngModel required minlength="4" #userRef="ngModel">

    @if (userRef.invalid && userRef.touched) {

      <span style="color: red;">Geçerli bir ad giriniz (En az 4 karakter).</span>

    }

  </div>

 

  <button type="submit" [disabled]="kullaniciFormu.invalid">Gönder</button>

</form>

2. Reaktif Formlar (Reactive Forms - Angular 19/22 Güncellemesi)

Form yapısı tamamen TypeScript tarafında programatik olarak kurulur. Büyük, dinamik ve test edilebilir formlar için idealdir. [5, 6, 8, 9]

import { Component } from '@angular/core';

import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';

 

@Component({

  selector: 'app-reactive-form',

  standalone: true,

  imports: [ReactiveFormsModule],

  template: `

    <form [formGroup]="profilFormu" (ngSubmit)="gonder()">

      <input formControlName="eposta" placeholder="E-posta adresiniz">

      @if (profilFormu.get('eposta')?.invalid && profilFormu.get('eposta')?.touched) {

        <p style="color: red;">Geçersiz e-posta formatı!</p>

      }

      <button type="submit" [disabled]="profilFormu.invalid">Kaydet</button>

    </form>

  `

})

export class ReactiveFormComponent {

  profilFormu = new FormGroup({

    eposta: new FormControl('', [Validators.required, Validators.email])

  });

 

  gonder() {

    console.log(this.profilFormu.value);

  }

}


MODÜL 9: Yönlendirme ve Navigasyon (Routing)

Uygulamanın sayfalar arasında yenilenmeden (Single Page Application) geçiş yapabilmesini sağlayan sistemdir.

1. Rotaların Tanımlanması ve Sayfa Geçişleri

// app.routes.ts

import { Routes } from '@angular/router';

import { HomeComponent } from './home/home.component';

 

export const routes: Routes = [

  { path: '', component: HomeComponent },

  // Lazy Loading: Bileşen sadece sayfa açıldığında tarayıcıya yüklenir (Performans artışı)

  {

    path: 'ayarlar',

    loadComponent: () => import('./settings/settings.component').then(m => m.SettingsComponent)

  },

  { path: 'kullanici/:id', loadComponent: () => import('./user/user.component').then(m => m.UserComponent) }

];

<!-- Ana Çerçeve Tasarımı (app.component.html) -->

<nav>

  <a routerLink="/" routerLinkActive="aktif-link" [routerLinkActiveOptions]="{exact: true}">Anasayfa</a>

  <a routerLink="/ayarlar" routerLinkActive="aktif-link">Ayarlar</a>

</nav>

 

<!-- Sayfaların yükleneceği dinamik alan -->

<router-outlet></router-outlet>

2. Dinamik Parametre Okuma (ActivatedRoute)

Link üzerinden gelen değişken değerleri (Örn: kullanici/5) sayfada okuyabiliriz.

import { Component, inject, OnInit } from '@angular/core';

import { ActivatedRoute } from '@angular/router';

 

@Component({

  selector: 'app-user',

  standalone: true,

  template: `<p>Görüntülenen Kullanıcı ID: {{ kullaniciId }}</p>`

})

export class UserComponent implements OnInit {

  private route = inject(ActivatedRoute);

  kullaniciId: string | null = '';

 

  ngOnInit() {

    // URL parametresini yakalama

    this.kullaniciId = this.route.snapshot.paramMap.get('id');

  }

}


MODÜL 10: Angular Signals (Yeni Nesil Reaktif Programlama)

Angular 22'nin en güçlü özelliği olan Signals, değer değişimlerini milisaniyeler içinde doğrudan algılayan reaktif bir veri sarıcıdır. Eski ağır çalışan genel kontrol mekanizmalarını devre dışı bırakarak performansı zirveye taşır. [1, 10, 11, 12]

1. Writable Signals (set, update)

Değeri doğrudan değiştirilebilen sinyallerdir. [11]

import { Component, signal, computed, effect } from '@angular/core';

 

@Component({

  selector: 'app-signal-counter',

  standalone: true,

  template: `

    <h3>Sayaç: {{ sayac() }}</h3>

    <h4>Katlanmış Değer (Computed): {{ katlanmisDeger() }}</h4>

    <button (click)="arttir()">Arttır (+1)</button>

    <button (click)="sifirla()">Sıfırla</button>

  `

})

export class SignalCounterComponent {

  // Sinyal tanımlama

  sayac = signal<number>(0);

 

  // Türetilmiş Sinyal (Computed): Sadece bağımlı olduğu sinyal değişirse tetiklenir

  katlanmisDeger = computed(() => this.sayac() * 2);

 

  constructor() {

    // Sinyal Takipçisi (Effect): Sinyal her değiştiğinde arka planda bu log çalışır

    effect(() => {

      console.log(`Güncel sayaç değeri yan etki üretti: ${this.sayac()}`);

    });

  }

 

  arttir() {

    // Güncelleme metodu (Mevcut değer üzerinden hesaplama yapar)

    this.sayac.update(mevcut => mevcut + 1);

  }

 

  sifirla() {

    // Doğrudan yeni değer atama metodu

    this.sayac.set(0);

  }

}


MODÜL 11: Bileşen Yaşam Döngüsü (Lifecycle Hooks)

Bir bileşen ekrana çizilirken, güncellenirken veya ekrandan tamamen silinirken belirli aşamalardan geçer. Bu aşamaları yakalayarak özel kodlar çalıştırabiliriz.

import { Component, OnInit, OnChanges, OnDestroy, SimpleChanges, Input } from '@angular/core';

 

@Component({

  selector: 'app-lifecycle-demo',

  standalone: true,

  template: `<p>Döngü İzleme Sayfası</p>`

})

export class LifecycleDemoComponent implements OnInit, OnChanges, OnDestroy {

  @Input() veri: string = '';

 

  constructor() {

    console.log('1. Constructor: Bileşen ilk kez hafızada oluşturuldu.');

  }

 

  ngOnChanges(changes: SimpleChanges) {

    console.log('2. ngOnChanges: Dışarıdan gelen @Input verisi değişti.', changes);

  }

 

  ngOnInit() {

    console.log('3. ngOnInit: Bileşen başarıyla ayağa kalktı. API istekleri için en ideal yerdir.');

  }

 

  ngOnDestroy() {

    console.log('4. ngOnDestroy: Kullanıcı sayfadan ayrıldı bileşen yok ediliyor. Temizlik işlemleri yapılır.');

  }

}

 

 

 

 

Angular 22 ile birlikte reaktif dünyada çok büyük bir dönüm noktasına ulaşıldı. Uzun süredir deneysel olarak geliştirilen Signal Forms API, asenkron reaktivite sağlayan resource() yapısı ve reaktif dünyayı birbirine bağlayan rxjs-interop araçları artık tamamen kararlı (stable) ve üretime hazır hale geldi. [1, 2, 3, 4]

Bu rehberde, eski FormGroup ve FormControl hantallıklarını, ControlValueAccessor karmaşalarını geride bırakarak tamamen sinyal tabanlı modern bir mimari kuracağız. Projemizde Signal Forms, RxJS-Signal Birlikte Çalışabilirliği (Interop) ve modern test aracı Vitest mimarisini uçtan uca ayağa kaldıracağız. [2, 5, 6, 7, 8, 9]


MİMARİ TASARIM VE PLANLAMA

Uygulamamız bir "Akıllı Kullanıcı Kayıt ve Şehir Eşleştirme" mini projesidir. Sistem şu şekilde çalışacaktır:

  1. Form State: Formun tüm verisi tek bir ham TypeScript nesnesini sarmalayan yazılabilir bir sinyal (signal()) içinde tutulacaktır.
  2. Schema Validation: Form kuralları merkezi bir şema yapısında bildirilecektir.
  3. RxJS - Signal Interoperability: Kullanıcı formda bir şehir ismi girdikçe, arka planda bir RxJS akışı tetiklenecek, sahte bir API'ye gidip asenkron olarak hava durumu ve koordinat verilerini çekecek ve bu veriyi anında bir Angular Sinyaline (toSignal) dönüştürecektir.
  4. Vitest: Projenin reaktif akışlarını doğrulamak için Karma/Jasmine yerine en modern ve hızlı test aracı olan Vitest kullanılacaktır. [2, 8, 9, 10, 11, 12]

ADIM 1: Signal Forms ile Mini Form Bileşeni

Öncelikle formun veri modelini (interface), doğrulama kurallarını barındıran şemasını ve bileşen kodunu hazırlayalım. [9, 10]

1. TypeScript Form Modeli ve Şeması

Signal Forms, klasik reaktif formların aksine doğrudan tip güvenli şemalarla çalışır. [5, 9, 11]

// user-form.model.ts

import { Validators } from '@angular/forms'; // Şema içindeki hazır validator'lar

 

export interface KayitModel {

  kullaniciAdi: string;

  eposta: string;

  sehir: string;

}

 

// Form başlangıç değerleri

export const ilkFormDegeri: KayitModel = {

  kullaniciAdi: '',

  eposta: '',

  sehir: ''

};

2. Bileşen Kodu (TypeScript)

Angular 22'de form oluşturmak için @angular/forms/signals paketinden gelen form() fonksiyonunu kullanırız. Bu fonksiyon bize tüm alanları otomatik olarak sinyale dönüştürülmüş bir ağaç yapısı (FieldTree) döndürür. [7, 9, 10, 13]

// user-form.component.ts

import { Component, signal, computed, inject } from '@angular/core';

import { form, [formField] } from '@angular/forms/signals';

import { JsonPipe } from '@angular/common';

import { KayitModel, ilkFormDegeri } from './user-form.model';

import { HavaDurumuServisi } from './hava-durumu.service';

 

@Component({

  selector: 'app-user-form',

  standalone: true,

  imports: [JsonPipe],

  templateUrl: './user-form.component.html',

  styleUrls: ['./user-form.component.css']

})

export class UserFormComponent {

  private havaServisi = inject(HavaDurumuServisi);

 

  // 1. Form datasını barındıran ana sinyal

  formModel = signal<KayitModel>(ilkFormDegeri);

 

  // 2. Form fonksiyonu ile FieldTree (Alan Ağacı) oluşturma ve doğrulama tanımlama

  kayitForm = form(this.formModel, {

    validators: {

      kullaniciAdi: (path) => path.value().length < 3 ? [{ kind: 'minlength', message: 'En az 3 karakter!' }] : null,

      eposta: (path) => !path.value().includes('@') ? [{ kind: 'email', message: 'Geçersiz e-posta!' }] : null,

      sehir: (path) => !path.value() ? [{ kind: 'required', message: 'Şehir boş bırakılamaz.' }] : null

    }

  });

 

  // 3. Asenkron veri çekme servisini formun 'sehir' alanındaki sinyale bağlıyoruz

  // Kullanıcı şehir girdikçe bu sinyal otomatik tetiklenecek

  sehirDegisimi = computed(() => this.kayitForm.sehir.value());

 

  // Servisten gelen reaktif asenkron veri (Detayları Adım 2'de)

  havaDurumuSinyali = this.havaServisi.getHavaDurumuSinyali(this.sehirDegisimi);

 

  formuGonder() {

    if (this.kayitForm.valid()) {

      console.log('Form Başarıyla Gönderildi:', this.formModel());

    } else {

      console.log('Formda hatalar var, lütfen düzeltiniz.');

    }

  }

}

3. Bileşen Arayüzü (HTML)

HTML tarafında girdileri form ağacına bağlamak için yeni [formField] direktifini kullanırız. Herhangi bir FormGroup tagına ihtiyaç yoktur. Tüm durumlar (valid, touched, errors) birer sinyaldir ve fonksiyon gibi () ile çağrılır. [9, 10, 13, 14]

<!-- user-form.component.html -->

<div class="form-container">

  <h2>Modern Signal Forms Kayıt Paneli</h2>

 

  <div class="form-field">

    <label>Kullanıcı Adı:</label>

    <input type="text" [formField]="kayitForm.kullaniciAdi" />

    @if (kayitForm.kullaniciAdi.invalid() && kayitForm.kullaniciAdi.touched()) {

      <span class="error">{{ kayitForm.kullaniciAdi.errors()[0].message }}</span>

    }

  </div>

 

  <div class="form-field">

    <label>E-posta:</label>

    <input type="email" [formField]="kayitForm.eposta" />

    @if (kayitForm.eposta.invalid() && kayitForm.eposta.touched()) {

      <span class="error">{{ kayitForm.eposta.errors()[0].message }}</span>

    }

  </div>

 

  <div class="form-field">

    <label>Şehir (Hava Durumu Bilgisi İçin):</label>

    <input type="text" [formField]="kayitForm.sehir" placeholder="Örn: Istanbul, London" />

  </div>

 

  <!-- Asenkron Veri Gösterim Alanı (RxJS -> Signal) -->

  @if (havaDurumuSinyali(); let hava) {

    <div class="async-box">

      @if (hava.yukleniyor) {

        <p class="loading">Hava durumu verisi asenkron olarak çekiliyor...</p>

      } @else if (hava.veri) {

        <div class="weather-info">

          <p><strong>Lokasyon:</strong> {{ hava.veri.lokasyon }}</p>

          <p><strong>Sıcaklık:</strong> {{ hava.veri.derece }}°C</p>

          <p><strong>Durum:</strong> {{ hava.veri.durum }}</p>

        </div>

      }

    </div>

  }

 

  <button (click)="formuGonder()" [disabled]="kayitForm.invalid()">Kayıt Ol</button>

</div>


ADIM 2: RxJS ve Sinyal Birlikte Çalışabilirliği (Interop) ile Asenkron Veri Çekme

Kullanıcının form alanına yazdığı şehir ismini (Signal<string>) alıp, bir RxJS boru hattından (Pipeline) geçirerek asenkron veri çeken ve sonucu tekrar sinyal olarak döndüren gelişmiş bir servis tasarlayalım. [8]

Bunun için @angular/core/rxjs-interop kütüphanesinden gelen toObservable ve toSignal fonksiyonlarını harmanlayacağız. [8]

// hava-durumu.service.ts

import { Injectable, Signal } from '@angular/core';

import { toObservable, toSignal } from '@angular/core/rxjs-interop';

import { Observable, of } from 'rxjs';

import { delay, switchMap, map, startWith, catchError } from 'rxjs/operators';

 

export interface HavaDurumuSonuc {

  yukleniyor: boolean;

  veri: { lokasyon: string; derece: number; durum: string } | null;

  hata: string | null;

}

 

@Injectable({

  providedIn: 'root'

})

export class HavaDurumuServisi {

 

  // Sahte API simülasyonu yapan fonksiyon

  private sahteApiCagrisi(sehir: string): Observable<{ lokasyon: string; derece: number; durum: string }> {

    const veriler: Record<string, { lokasyon: string; derece: number; durum: string }> = {

      istanbul: { lokasyon: 'İstanbul, TR', derece: 28, durum: 'Güneşli' },

      london: { lokasyon: 'London, UK', derece: 16, durum: 'Yağmurlu' },

      berlin: { lokasyon: 'Berlin, DE', derece: 21, durum: 'Bulutlu' }

    };

 

    const anahtar = sehir.toLowerCase().trim();

    const bulunanVeri = veriler[anahtar] || { lokasyon: `${sehir}`, derece: 20, durum: 'Bilinmiyor' };

 

    // API gecikmesini simüle etmek için 800ms delay ekliyoruz

    return of(bulunanVeri).pipe(delay(800));

  }

 

  // Sinyal alan ve asenkron işlemlerden sonra geriye Sinyal döndüren gelişmiş fonksiyon

  getHavaDurumuSinyali(sehirSinyali: Signal<string>): Signal<HavaDurumuSonuc> {

   

    // 1. Sinyali RxJS Observable akışına dönüştürüyoruz

    const sehir$ = toObservable(sehirSinyali);

 

    const akis$ = sehir$.pipe(

      switchMap((sehir) => {

        if (!sehir || sehir.trim() === '') {

          return of({ yukleniyor: false, veri: null, hata: null });

        }

 

        // 2. Her yeni şehir girişinde eski isteği iptal edip (switchMap) yeni API çağrısı başlatıyoruz

        return this.sahteApiCagrisi(sehir).pipe(

          map(veri => ({ yukleniyor: false, veri, hata: null })),

          // İstek başladığı an "yükleniyor" durumuna geçiyoruz

          startWith({ yukleniyor: true, veri: null, hata: null }),

          catchError(err => of({ yukleniyor: false, veri: null, hata: 'Veri çekilemedi!' }))

        );

      })

    );

 

    // 3. RxJS akışını bileşenin doğrudan okuyabileceği kararlı bir Sinyale dönüştürüyoruz

    return toSignal(akis$, {

      initialValue: { yukleniyor: false, veri: null, hata: null }

    });

  }

}


ADIM 3: Vitest ile Reaktif Süreçlerin ve Formun Test Edilmesi

Angular 22 mimarisinde Karma/Jasmine yerine yüksek hız sunan Vitest entegrasyonu modern projelerin standardıdır. Sinyal tabanlı yapıların doğruluğunu test etmek, eski yapılara göre çok daha basittir çünkü tetikleme mekanizmaları doğrudan fonksiyon çağrıları ile izlenebilir. [2, 7, 15]

Aşağıda formun doğrulama (validation) mekanizmasını ve asenkron reaktif akışları test eden Vitest kurgusu yer almaktadır.

// user-form.component.spec.ts

import { describe, it, expect, beforeEach, vi } from 'vitest';

import { TestBed, fakeAsync, tick } from '@angular/core/testing';

import { UserFormComponent } from './user-form.component';

import { HavaDurumuServisi } from './hava-durumu.service';

import { of } from 'rxjs';

import { delay } from 'rxjs/operators';

 

describe('UserFormComponent - Angular 22 Signal Forms & Interop Vitest Testleri', () => {

  let component: UserFormComponent;

  let sahteHavaServisi: any;

 

  beforeEach(async () => {

    // Sahte servis kurgusu (Mocking)

    sahteHavaServisi = {

      getHavaDurumuSinyali: vi.fn().mockImplementation((sehirSinyali) => {

        // Test esnasında karmaşık asenkron süreçleri simüle etmek için basit bir reaktif dönüşüm

        return () => ({

          yukleniyor: false,

          veri: sehirSinyali() === 'Istanbul' ? { lokasyon: 'İstanbul, TR', derece: 28, durum: 'Güneşli' } : null,

          hata: null

        });

      })

    };

 

    await TestBed.configureTestingModule({

      imports: [UserFormComponent],

      providers: [

        { provide: HavaDurumuServisi, useValue: sahteHavaServisi }

      ]

    }).compileComponents();

 

    const fixture = TestBed.createComponent(UserFormComponent);

    component = fixture.componentInstance;

  });

 

  it('Bileşen ilk açıldığında form geçersiz (invalid) olmalıdır', () => {

    // Başlangıç değerleri boş olduğu için form invalid olmalı

    expect(component.kayitForm.valid()).toBe(false);

    expect(component.kayitForm.invalid()).toBe(true);

  });

 

  it('Kurallara uygun veri girildiğinde form geçerli (valid) duruma geçmelidir', () => {

    // Signal formun değerlerini set metodu ile güncelliyoruz

    component.formModel.set({

      kullaniciAdi: 'Ahmet',

      eposta: 'ahmet@eposta.com',

      sehir: 'Istanbul'

    });

 

    // Değer değişiminden sonra form geçerlilik durumunu kontrol ediyoruz

    expect(component.kayitForm.kullaniciAdi.invalid()).toBe(false);

    expect(component.kayitForm.eposta.invalid()).toBe(false);

    expect(component.kayitForm.valid()).toBe(true);

  });

 

  it('Kullanıcı adı 3 karakterden az olduğunda ilgili alan hata vermelidir', () => {

    component.formModel.set({

      kullaniciAdi: 'Al', // Geçersiz: 3 karakterden az

      eposta: 'deneme@test.com',

      sehir: 'Izmir'

    });

 

    const adAlani = component.kayitForm.kullaniciAdi;

    expect(adAlani.invalid()).toBe(true);

    expect(adAlani.errors()[0].message).toBe('En az 3 karakter!');

  });

 

  it('Form sinyali değiştiğinde asenkron servis entegrasyonu doğru çalışmalıdır', () => {

    // Şehir sinyalini değiştiriyoruz

    component.formModel.set({

      kullaniciAdi: 'Mehmet',

      eposta: 'mehmet@test.com',

      sehir: 'Istanbul'

    });

 

    // Servis fonksiyonunun çağrılıp çağrılmadığını kontrol ediyoruz

    expect(sahteHavaServisi.getHavaDurumuSinyali).toHaveBeenCalled();

   

    // Hava durumu sinyalinin ürettiği değeri doğrula

    const havaDurumu = component.havaDurumuSinyali();

    expect(havaDurumu.veri?.lokasyon).toBe('İstanbul, TR');

    expect(havaDurumu.veri?.derece).toBe(28);

  });

});


PROJENİN TARAYICIDA ÇALIŞMA ÖZETİ (Zoneless ve Signal Avantajı)

Bu yazdığımız kodlar tarayıcıda çalışırken arka planda şu modern süreçler gerçekleşir:

  1. Sıfır Zone.js Yükü (Zoneless): Uygulama çalışırken tarayıcıdaki her tıklamada veya asenkron işlemde tüm sayfayı baştan aşağı kontrol eden eski Zone.js mekanizması devreye girmez. Değişiklik algılama (Change Detection) sadece değeri değişen sinyalin bağlı olduğu HTML hücresinde lokal olarak gerçekleşir. [1, 2, 12, 16]
  2. Otomatik İptal Mekanizması: Kullanıcı şehir kutusuna "I-s-t-a-n-b-u-l" yazarken, her harfte RxJS switchMap yapısı sayesinde bir önceki harfin asenkron HTTP isteği tarayıcının ağ (Network) sekmesinde otomatik olarak iptal edilir (Cancel). Bu da sunucuya gereksiz yük gitmesini önler.
  3. Hafıza Dostu Tasarım: Sinyaller kendi yaşam döngülerini yönettikleri için toSignal ile bağladığımız RxJS abonelikleri (Subscription), bileşen ekrandan kaldırıldığında (ngOnDestroy anında) bellek sızıntısına (Memory Leak) yol açmadan otomatik olarak yok edilir.

 

Angular 22 ile kararlı (stable) sürüme ulaşan httpResource() mimarisi, dinamik listeleri yöneten Signal Form Array yapısı ve Vitest tabanlı tarayıcı simülasyon testleri (Component Testing) ile projemizi uçtan uca güncelliyoruz. [1, 2, 3, 4]

Bu entegrasyonda sahte gecikmeleri tamamen kaldırarak, gerçek bir açık API olan Open-Meteo Geocoding & Weather API servislerini doğrudan Angular'ın yeni reaktif ağ katmanına bağlayacağız.


ADIM 1: Model Güncellemesi (Form Array ve API Nesneleri)

Formumuzun içine dinamik olarak yeni "Hobi" veya "İlgi Alanı" metin kutuları ekleyip çıkartabileceğimiz bir dizi yapısı ekliyoruz. Aynı zamanda API'den dönecek nesne tiplerini netleştiriyoruz.

// user-form.model.ts

export interface KayitModel {

  kullaniciAdi: string;

  eposta: string;

  sehir: string;

  hobiler: string[]; // Dinamik Form Dizisi (Signal Form Array) için alan

}

 

export const ilkFormDegeri: KayitModel = {

  kullaniciAdi: '',

  eposta: '',

  sehir: '',

  hobiler: ['Kod Yazmak'] // Başlangıçta bir adet hobi ile açılıyor

};

 

// Gerçek Open-Meteo API'den dönecek yanıt modeli

export interface HavaDurumuResponse {

  current_weather: {

    temperature: number;

    windspeed: number;

    weathercode: number;

  };

}


ADIM 2: httpResource() ile Gerçek Zamanlı Asenkron Veri Çekme

Angular 22 ile birlikte gelen httpResource(), HttpClient bağımlılığını reaktif sinyallere bağlayan en kararlı yapıdır. RxJS boru hatlarına ihtiyaç duymadan, şablondan gelen bir sinyal değiştiği an ağ isteğini otomatik olarak tetikler ve iptal süreçlerini yönetir. [2, 5, 6]

// hava-durumu.service.ts

import { Injectable, Signal } from '@angular/core';

import { httpResource } from '@angular/common/http'; // Angular 22 Kararlı Paket

import { HavaDurumuResponse } from './user-form.model';

 

@Injectable({

  providedIn: 'root'

})

export class HavaDurumuServisi {

 

  // Şehir koordinatlarını bulup hava durumunu getiren fonksiyon

  getHavaDurumuSinyali(sehirSinyali: Signal<string>) {

   

    // httpResource() reaktif bir fonksiyon alır ve url bağımlılığını otomatik izler

    return httpResource<HavaDurumuResponse>(() => {

      const sehir = sehirSinyali().trim().toLowerCase();

     

      if (!sehir) return null;

 

      // Gerçek zamanlı açık hava durumu API koordinat eşleştirmeleri (Simüle Entegrasyon)

      let lat = 41.0082, lon = 28.9784; // Varsayılan İstanbul

      if (sehir === 'london') { lat = 51.5074; lon = -0.1278; }

      if (sehir === 'berlin') { lat = 52.5200; lon = 13.4050; }

 

      // Geriye dönen URL dizesi değiştikçe istek otomatik tekrarlanır

      return `https://open-meteo.com{lat}&longitude=${lon}&current_weather=true`;

    });

  }

}


ADIM 3: Bileşen ve Dinamik Form Array Entegrasyonu

Bileşende @angular/forms/signals paketinin form array yeteneklerini kullanıyoruz. Şablon içinde hobiler dizisini manipüle etmek için dinamik ekleme ve çıkarma metotları yazıyoruz. [7]

1. Bileşen Kodu (TypeScript)

// user-form.component.ts

import { Component, signal, computed, inject } from '@angular/core';

import { form } from '@angular/forms/signals';

import { KeyValuePipe } from '@angular/common';

import { KayitModel, ilkFormDegeri } from './user-form.model';

import { HavaDurumuServisi } from './hava-durumu.service';

 

@Component({

  selector: 'app-user-form',

  standalone: true,

  imports: [KeyValuePipe],

  templateUrl: './user-form.component.html',

  styleUrls: ['./user-form.component.css']

})

export class UserFormComponent {

  private havaServisi = inject(HavaDurumuServisi);

 

  // 1. Ana Model Sinyali

  formModel = signal<KayitModel>(ilkFormDegeri);

 

  // 2. Şema Yapısı ve Doğrulamalar

  kayitForm = form(this.formModel, {

    validators: {

      kullaniciAdi: (path) => path.value().length < 3 ? [{ kind: 'min', message: 'En az 3 harf!' }] : null,

      sehir: (path) => !path.value() ? [{ kind: 'required', message: 'Şehir giriniz.' }] : null

    }

  });

 

  // 3. httpResource Entegrasyonu

  sehirDegisimi = computed(() => this.kayitForm.sehir.value());

  havaDurumuResource = this.havaServisi.getHavaDurumuSinyali(this.sehirDegisimi);

 

  // --- Dynamic Signal Form Array İşlemleri ---

  hobiEkle() {

    this.formModel.update(mevcut => ({

      ...mevcut,

      hobiler: [...mevcut.hobiler, ''] // Dizinin sonuna boş bir hobi girdisi ekler

    }));

  }

 

  hobiCikar(index: number) {

    this.formModel.update(mevcut => ({

      ...mevcut,

      hobiler: mevcut.hobiler.filter((_, i) => i !== index) // Belirtilen indeksteki hobi alanını siler

    }));

  }

 

  formuGonder() {

    if (this.kayitForm.valid()) {

      console.log('Başarılı Veri:', this.formModel());

    }

  }

}

2. Şablon Yapısı (HTML)

Dinamik diziyi ekranda listelemek için @for döngüsünü kayitForm.hobiler alan ağacı üzerinde döndürüyoruz.

<!-- user-form.component.html -->

<div class="form-wrapper">

  <h2>Gelişmiş API ve Dinamik Form Paneli</h2>

 

  <!-- Standart Alanlar -->

  <div class="field">

    <label>Kullanıcı Adı:</label>

    <input type="text" [formField]="kayitForm.kullaniciAdi" id="username-input" />

  </div>

 

  <div class="field">

    <label>Şehir Kontrolü (httpResource):</label>

    <input type="text" [formField]="kayitForm.sehir" id="city-input" />

  </div>

 

  <!-- HTTP RESOURCE GERÇEK ZAMANLI VERİ GÖSTERİMİ -->

  @if (havaDurumuResource.isLoading()) {

    <p class="status-msg">Açık API üzerinden canlı veriler çekiliyor...</p>

  } @else if (havaDurumuResource.value(); let veri) {

    <div class="weather-card" id="weather-result">

      <p>☀️ <strong>Sıcaklık:</strong> {{ veri.current_weather.temperature }}°C</p>

      <p>💨 <strong>Rüzgar Hızı:</strong> {{ veri.current_weather.windspeed }} km/s</p>

    </div>

  }

 

  <!-- DINAMIK FORM DIZISI (SIGNAL FORM ARRAY) ALANI -->

  <div class="array-section">

    <h3>Dinamik İlgi Alanları / Hobiler</h3>

    <button type="button" (click)="hobiEkle()" id="add-hobby-btn">+ Yeni Hobi Ekle</button>

 

    @for (hobiField of kayitForm.hobiler; track $index) {

      <div class="array-item">

        <input type="text" [formField]="hobiField" class="hobby-input" [attr.data-index]="$index" />

        <button type="button" (click)="hobiCikar($index)" class="remove-hobby-btn">Sil</button>

      </div>

    }

  </div>

 

  <button (click)="formuGonder()" [disabled]="kayitForm.invalid()" id="submit-btn">Kaydet</button>

</div>


ADIM 4: Vitest ile Tarayıcı Ortamı Simülasyonu (Component Testing)

Vitest ve @testing-library/angular veya standart Angular TestBed araçlarını birleştirerek, kullanıcının gerçek dünyada butonlara basıp alanları manipüle etmesini simüle eden görsel/etkileşimli testleri kurguluyoruz. [4]

// user-form.component.spec.ts

import { describe, it, expect, beforeEach, vi } from 'vitest';

import { TestBed, ComponentFixture } from '@angular/core/testing';

import { UserFormComponent } from './user-form.component';

import { HavaDurumuServisi } from './hava-durumu.service';

 

describe('UserFormComponent - Vitest Tarayıcı Simülasyon Testleri', () => {

  let component: UserFormComponent;

  let fixture: ComponentFixture<UserFormComponent>;

  let sahteHavaServisi: any;

 

  beforeEach(async () => {

    // httpResource yanıtını simüle eden sahte bir sinyal yapısı kuruyoruz

    sahteHavaServisi = {

      getHavaDurumuSinyali: vi.fn().mockReturnValue({

        isLoading: () => false,

        value: () => ({

          current_weather: { temperature: 24, windspeed: 12, weathercode: 1 }

        }),

        error: () => null

      })

    };

 

    await TestBed.configureTestingModule({

      imports: [UserFormComponent],

      providers: [

        { provide: HavaDurumuServisi, useValue: sahteHavaServisi }

      ]

    }).compileComponents();

 

    fixture = TestBed.createComponent(UserFormComponent);

    component = fixture.componentInstance;

    fixture.detectChanges(); // Tarayıcı DOM ağacını ilk kez tetikler

  });

 

  it('Kullanıcı "+ Yeni Hobi Ekle" butonuna tıkladığında ekrana yeni bir girdi kutusu eklenmelidir', async () => {

    // 1. Başlangıçta kaç hobi girdisi olduğunu doğrula (Modelde 1 tanımlamıştık)

    let girdiKutulari = fixture.nativeElement.querySelectorAll('.hobby-input');

    expect(girdiKutulari.length).toBe(1);

 

    // 2. Tarayıcıdaki ekleme butonunu bul ve tıklama simülasyonu yap

    const ekleButonu = fixture.nativeElement.querySelector('#add-hobby-btn');

    ekleButonu.click();

   

    // 3. Sinyal güncellendiği için DOM'un yenilenmesini sağla

    fixture.detectChanges();

 

    // 4. Yeni kutunun DOM ağacına başarıyla eklenip eklenmediğini doğrula

    girdiKutulari = fixture.nativeElement.querySelectorAll('.hobby-input');

    expect(girdiKutulari.length).toBe(2);

  });

 

  it('Kullanıcı sil butonuna bastığında ilgili hobi alanı tarayıcı ekranından kaldırılmalıdır', async () => {

    // Test senaryosu için ikinci bir alan ekleyelim

    component.hobiEkle();

    fixture.detectChanges();

 

    let girdiKutulari = fixture.nativeElement.querySelectorAll('.hobby-input');

    expect(girdiKutulari.length).toBe(2);

 

    // İlk girdinin yanındaki silme butonunu tetikle

    const silButonu = fixture.nativeElement.querySelector('.remove-hobby-btn');

    silButonu.click();

    fixture.detectChanges();

 

    // Girdilerin bire düştüğünü doğrula

    girdiKutulari = fixture.nativeElement.querySelectorAll('.hobby-input');

    expect(girdiKutulari.length).toBe(1);

  });

 

  it('httpResource veriyi başarılı çektiğinde hava durumu kartı ekranda render edilmelidir', () => {

    // Şehir bilgisini set edelim

    component.formModel.update(m => ({ ...m, sehir: 'London' }));

    fixture.detectChanges();

 

    // Şablondaki hava durumu kartını seçelim

    const kartElementi = fixture.nativeElement.querySelector('#weather-result');

   

    expect(kartElementi).toBeTruthy();

    expect(kartElementi.textContent).toContain('24°C');

    expect(kartElementi.textContent).toContain('12 km/s');

  });

});

 

 

 

 

Angular 22 ile birlikte kararlı (stable) hale gelen Signal Forms Submission API, harici sunucu (backend) hatalarını doğrudan form ağacına besleyebilen reaktif bir hata yönetim mimarisi sunar. Bu bölümde, sunucu hatası anında tüm girdileri otomatik kilitleyen (read-only/disabled), üst kısımda toplu hata özeti (Error Summary) gösteren gelişmiş stratejiyi entegre ediyoruz. Ardından projeyi tamamen Zoneless (Zone.js olmadan) moda geçirerek tarayıcı işlemci ve bellek analiz süreçlerini kurguluyoruz. [1, 2, 3, 4, 5, 6]


ADIM 1: Gelişmiş Sunucu Hata Yönetimi (Submission Error Handling)

Signal Forms paketinde yer alan submit() API'si, asenkron form gönderim süreçlerinde form ağacının kilitlenme ve hata fırlatma durumlarını tamamen sinyaller üzerinden yönetir. [3, 4]

1. Bileşen Kod Yapısı (TypeScript)

submit() fonksiyonunun içerisindeki geri çağırım (callback) mekanizması, sunucudan dönen alan bazlı veya global hataları yakalayarak tüm formu reaktif olarak kilitler. [1, 3]

// user-form.component.ts

import { Component, signal, computed, inject } from '@angular/core';

import { form, submit } from '@angular/forms/signals'; // Angular 22 Form Submission API

import { KayitModel, ilkFormDegeri } from './user-form.model';

 

@Component({

  selector: 'app-user-form',

  standalone: true,

  templateUrl: './user-form.component.html'

})

export class UserFormComponent {

  formModel = signal<KayitModel>(ilkFormDegeri);

 

  // Form şeması tanımı

  kayitForm = form(this.formModel, {

    validators: {

      kullaniciAdi: (p) => p.value().length < 3 ? [{ kind: 'min', message: 'En az 3 harf!' }] : null,

      sehir: (p) => !p.value() ? [{ kind: 'required', message: 'Şehir zorunlu.' }] : null

    }

  });

 

  // Sunucu (Submission) kaynaklı hataları toplu göstermek için özel durum sinyali

  sunucuHataOzeti = signal<string[]>([]);

 

  // Formun kilitlenme (Disabled) durumu: Form gönderiliyorken VEYA kritik sunucu hatası varken

  formKilitliMi = computed(() => {

    return this.kayitForm.submitting() || this.sunucuHataOzeti().length > 0; // submitting() hazır sinyaldir

  });

 

  async onSubmit() {

    // Toplu hata özetini temizle

    this.sunucuHataOzeti.set([]);

 

    // Angular 22 submit() API entegrasyonu

    await submit(this.kayitForm, async (formInstance) => {

      try {

        const payload = formInstance().value();

       

        // Sahte API / Sunucu Gönderim Simülasyonu

        if (payload.kullaniciAdi.toLowerCase() === 'admin') {

          // İş kuralı hatası: Sunucu bu ismi reddediyor

          throw new Error('BU_ISIM_ALINMIS: "admin" kullanıcı adı sunucu tarafından engellenmiştir.');

        }

       

        console.log('Sunucu kaydı başarılı:', payload);

      } catch (err: any) {

        // Sunucu hatasını yakala ve toplu gösterim dizisine ekle

        const mesaj = err.message || 'Bilinmeyen bir sunucu hatası oluştu.';

        this.sunucuHataOzeti.set([mesaj, 'Lütfen verilerinizi kontrol edip sayfayı yenileyiniz.']);

       

        // Alan bazlı hataları doğrudan Signal Form ağacına geri besleme (Mapping)

        if (mesaj.includes('BU_ISIM_ALINMIS')) {

          this.kayitForm.kullaniciAdi.setErrors([{ kind: 'server', message: 'Bu kullanıcı adı zaten kullanımda.' }]);

        }

      }

    });

  }

 

  formuSifirla() {

    this.formModel.set(ilkFormDegeri);

    this.sunucuHataOzeti.set([]);

  }

}

2. Şablon Mimarisi ve Toplu Hatalar (HTML)

Form elemanlarını kilitli duruma getirmek için şablondaki girdileri [disabled]="formKilitliMi()" sinyaline bağlıyoruz. Üst kısımda ise @if kontrolüyle toplu hata panelini listeliyoruz. [4]

<!-- user-form.component.html -->

<div class="form-wrapper">

 

  <!-- TOPLU SUNUCU HATA ÖZET PANELİ (ERROR SUMMARY) -->

  @if (sunucuHataOzeti().length > 0) {

    <div class="error-summary-box" id="error-summary">

      <h4>🚨 Form Gönderim Hatası (Sunucu Reddi)</h4>

      <ul>

        @for (hata of sunucuHataOzeti(); track hata) {

          <li>{{ hata }}</li>

        }

      </ul>

      <button (click)="formuSifirla()">Form Kilidini Aç ve Temizle</button>

    </div>

  }

 

  <form (ngSubmit)="onSubmit()">

    <div class="field">

      <label>Kullanıcı Adı:</label>

      <input type="text" [formField]="kayitForm.kullaniciAdi" [disabled]="formKilitliMi()" />

      @if (kayitForm.kullaniciAdi.invalid()) {

        <span class="err">{{ kayitForm.kullaniciAdi.errors().message }}</span>

      }

    </div>

 

    <div class="field">

      <label>Şehir:</label>

      <input type="text" [formField]="kayitForm.sehir" [disabled]="formKilitliMi()" />

    </div>

 

    <!-- Gönderim Esnasında Yükleniyor Durumu Yönetimi -->

    <button type="submit" [disabled]="kayitForm.invalid() || formKilitliMi()" id="submit-btn">

      @if (kayitForm.submitting()) {

        <span>Sunucuya Kaydediliyor...</span>

      } @else {

        <span>Formu Gönder</span>

      }

    </button>

  </form>

</div>


ADIM 2: Projeyi Zoneless Modda Çalıştırmak

Angular 22 projelerinde Zone.js bağımlılığını tamamen kaldırmak, uygulamanın gereksiz değişiklik algılama (Change Detection) turlarını sıfırlayarak performansı maksimize eder. [5, 7]

1. Polyfills Temizliği

src/polyfills.ts veya angular.json içerisindeki zone.js yüklemeleri kaldırılır veya yorum satırına alınır. [8]

2. Bootstrap Yapılandırması (main.ts)

Uygulamayı başlatırken provideZonelessChangeDetection() fonksiyonu eklenerek Angular'a tetiklemeleri sadece sinyaller üzerinden takip etmesi bildirilir. [6, 8]

// src/main.ts

import { bootstrapApplication } from '@angular/platform-browser';

import { provideZonelessChangeDetection } from '@angular/core'; // Zoneless Ayarı

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

import { appConfig } from './app/app.config';

 

bootstrapApplication(AppComponent, {

  providers: [

    provideZonelessChangeDetection(), // Zone.js olmadan çalıştırma motoru active edildi

    ...appConfig.providers

  ]

}).catch(err => console.error(err));


ADIM 3: Tarayıcı Performans ve Bellek Analiz Metodolojisi

Zoneless modun getirdiği 15-20% civarındaki bellek tasarrufunu ve azalan CPU yükünü tarayıcı geliştirici araçları üzerinden şu adımlarla doğrularız: [6]

1. CPU ve Değişiklik Algılama (Change Detection) Profil Analizi

2. Heap Memory (Bellek/Hafıza) Sızıntısı Analizi


ADIM 4: Vitest ile DOM Etkileşimli Bileşen Testleri

Kullanıcının form alanına "admin" yazıp gönder butonuna basmasıyla tetiklenen hata senaryosunu ve form elemanlarının kilitlenme simülasyonunu test eden etkileşimli Vitest kurgusu:

// user-form.component.spec.ts

import { describe, it, expect, beforeEach } from 'vitest';

import { TestBed, ComponentFixture } from '@angular/core/testing';

import { UserFormComponent } from './user-form.component';

 

describe('UserFormComponent - Sunucu Hatası ve Kilitleme Süreçleri (Vitest Component Test)', () => {

  let component: UserFormComponent;

  let fixture: ComponentFixture<UserFormComponent>;

 

  beforeEach(async () => {

    await TestBed.configureTestingModule({

      imports: [UserFormComponent]

    }).compileComponents();

 

    fixture = TestBed.createComponent(UserFormComponent);

    component = fixture.componentInstance;

    fixture.detectChanges(); // DOM ağacını oluşturur

  });

 

  it('Kullanıcı adı "admin" girilip form gönderildiğinde, tüm girdiler kilitlenmeli ve hata özeti gösterilmelidir', async () => {

    // 1. Form şemasına sunucunun reddedeceği veriyi set et

    component.formModel.set({

      kullaniciAdi: 'admin',

      eposta: 'yonetici@sirket.com',

      sehir: 'Ankara'

    });

    fixture.detectChanges();

 

    // 2. Şablondaki submit butonunu bul ve tıkla (Tarayıcı Simülasyonu)

    const submitButonu = fixture.nativeElement.querySelector('#submit-btn') as HTMLButtonElement;

    expect(submitButonu.disabled).toBe(false); // Form kuralları geçerli olduğundan buton aktif olmalı

   

    // Tıklama eylemini gerçekleştir

    submitButonu.click();

   

    // Asenkron submit() tamamlandıktan sonra DOM'u güncelle

    await fixture.whenStable();

    fixture.detectChanges();

 

    // 3. DOĞRULAMA: Form kilit mekanizmasının aktifleştiğini teyit et

    expect(component.formKilitliMi()).toBe(true);

 

    // 4. DOĞRULAMA: HTML üzerinde hata özet kutusunun (Error Summary) render edildiğini doğrula

    const hataKutusu = fixture.nativeElement.querySelector('#error-summary');

    expect(hataKutusu).toBeTruthy();

    expect(hataKutusu.textContent).toContain('admin kullanıcı adı sunucu tarafından engellenmiştir.');

 

    // 5. DOĞRULAMA: Butonun kilitlendiğini (disabled) doğrula

    expect(submitButonu.disabled).toBe(true);

  });

});