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:
Ş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.
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.
3. npm install -g @angular/cli
Adım 3: İlk Projeyi Oluşturma ve Çalıştırma
3. ng new ilk-projem
(Sistem size bazı sorular sorabilir;
yönlendirmeler için yön tuşlarıyla "CSS" seçip Enter
diyebilirsiniz).
5. cd ilk-projem
7. ng serve
🔄 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ı?
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:
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:
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}¤t_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);
});
});