Angular 20: Stable Signal API'leri, httpResource ve Yeni Dosya Adlandırma
Angular 20'de effect, toSignal ve linkedSignal stable oluyor, resource'ta request yerini params'a bırakıyor, httpResource geliyor, app.component.ts yerini app.ts'e veriyor ve *ngIf deprecated oluyor.
Sürüm notu: Bu yazı Angular 20 ile gelen yenilikleri anlatır; kodlar Angular 20 içindir.
Signal Dünyası Olgunlaşıyor
Angular 19'da standalone'un varsayılan olduğunu, linkedSignal ve deneysel resource() ile tanıştığımızı gördük. 28 Mayıs 2025'te çıkan Angular 20 ise daha çok bir toparlanma sürümü: denemesi süren API'lerin çoğu stable oluyor, resource API'si isim değiştiriyor ve CLI'nin ürettiği dosyalar ilk kez bambaşka görünüyor.
Bu yazıda sırasıyla stable olan signal API'lerine, resource tarafındaki değişikliğe, httpResource'a, yeni dosya adlandırmasına, eski structural directive'lerin akıbetine, zoneless'ın durumuna ve template ifadelerindeki küçük yeniliklere bakacağız.
Stable Olan Signal API'leri
Angular 12'de ekranda saniyede bir güncellenen bir saat göstermek için şunu yazardık:
export class ClockComponent implements OnInit, OnDestroy {
now = new Date();
private sub?: Subscription;
ngOnInit(): void {
this.sub = interval(1000).subscribe(() => this.now = new Date());
}
ngOnDestroy(): void {
this.sub?.unsubscribe();
}
}
Subscribe et, referansı sakla, component yok olurken unsubscribe etmeyi unutma. Angular 20'de aynı iş şöyle:
import { Component, effect } from '@angular/core';
import { DatePipe } from '@angular/common';
import { toSignal } from '@angular/core/rxjs-interop';
import { interval, map } from 'rxjs';
@Component({
selector: 'app-clock',
imports: [DatePipe],
template: `<p>{{ now() | date:'HH:mm:ss' }}</p>`
})
export class Clock {
protected readonly now = toSignal(
interval(1000).pipe(map(() => new Date())),
{ initialValue: new Date() }
);
constructor() {
effect(() => console.log('Saat:', this.now()));
}
}
toSignal observable'a kendisi subscribe oluyor ve component yok olduğunda aboneliği kendisi kapatıyor. effect ise okuduğu signal her değiştiğinde çalışıyor. İkisini de daha önce gördük; fark şu ki Angular 20'de effect(), toSignal(), toObservable() ve linkedSignal() artık stable. Yanlarında afterRenderEffect(), afterNextRender() ve PendingTasks da stable oldu. afterRender() ise afterEveryRender() adını aldı.
Class adının ClockComponent değil de Clock olması gözüne çarpmış olabilir. Bunun sebebini birazdan anlatacağım.
Resource API'sinde request Artık params
Bir önceki yazıda aklının bir köşesinde tutmanı istediğim parametrenin adı değişti:
// Angular 19
usersResource = resource({
request: () => ({ sort: this.sort() }),
loader: ({ request }) => fetchUsers(request.sort)
});
// Angular 20
usersResource = resource({
params: () => ({ sort: this.sort() }),
loader: ({ params }) => fetchUsers(params.sort)
});
Mantık aynı, sadece isim daha açıklayıcı: bu fonksiyon bir istek değil, loader'a gidecek parametreleri üretiyor. rxResource tarafında da loader adı stream oldu:
usersResource = rxResource({
params: () => this.sort(),
stream: ({ params }) => this.http.get<User[]>(`/api/users?sort=${params}`)
});
Bir de reload() metodu artık sadece yazılabilir resource'larda var. Unutma, resource API'leri Angular 20'de hâlâ experimental. Bu isim değişikliği de tam olarak "experimental" etiketinin ne anlama geldiğini gösteriyor: API, geri bildirimlere göre şekil değiştirebilir.
httpResource: HttpClient'ın Signal Hâli
Angular 12'de bir kullanıcı profilini çekmek için constructor'da HttpClient'ı alır, ngOnInit içinde subscribe olurduk:
constructor(private http: HttpClient) { }
ngOnInit(): void {
this.http.get<User>(`/api/users/${this.userId}`)
.subscribe(user => this.user = user);
}
19.2'de gelen ve Angular 20'de de experimental olarak duran httpResource bunu bir satıra indiriyor:
import { Component, input } from '@angular/core';
import { httpResource } from '@angular/common/http';
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-profile',
template: `
@if (user.value(); as u) {
<h3>{{ u.name }}</h3>
<p>{{ u.email }}</p>
} @else if (user.isLoading()) {
<p>Yükleniyor...</p>
} @else if (user.error()) {
<p>Kullanıcı bulunamadı</p>
}
`
})
export class UserProfile {
readonly userId = input.required<number>();
protected readonly user = httpResource<User>(() => `/api/users/${this.userId()}`);
}
httpResource'a URL döndüren bir fonksiyon veriyorsun. Fonksiyon userId signal'ını okuduğu için parent yeni bir id gönderdiğinde istek kendiliğinden tekrarlanıyor. Angular 20'de URL'yi düz string olarak değil, mutlaka böyle bir fonksiyonla vermen gerekiyor.
Arka planda HttpClient çalıştığı için interceptor'ların aynen devrede kalıyor; app.config.ts içinde provideHttpClient() olması yeterli. Okuma işleri için biçilmiş kaftan. Kaydetme ve silme gibi işlemlerde ise HttpClient'ı doğrudan kullanmaya devam ediyorsun.
Yeni Dosya Adları: app.component.ts Gidiyor, app.ts Geliyor
Angular 20 ile birlikte resmi stil rehberi büyük ölçüde güncellendi ve en görünür değişiklik dosya adlarında. Angular 12'de ng g c product-card şunu üretiyordu:
CREATE src/app/product-card/product-card.component.css
CREATE src/app/product-card/product-card.component.html
CREATE src/app/product-card/product-card.component.spec.ts
CREATE src/app/product-card/product-card.component.ts
UPDATE src/app/app.module.ts
Angular 20'de aynı komutun çıktısı:
CREATE src/app/product-card/product-card.css
CREATE src/app/product-card/product-card.html
CREATE src/app/product-card/product-card.spec.ts
CREATE src/app/product-card/product-card.ts
Dosya adlarından .component kalktı, class adı da ProductCardComponent yerine ProductCard oldu. app.module.ts güncellemesi de yok, çünkü güncellenecek bir module yok. Yeni bir projede kök component artık app.ts, app.html, app.css dosyalarında duruyor ve adı App.
Aynı kural directive ve service'ler için de geçerli: ng g s user artık user.ts dosyasında User adında bir class üretiyor. Pipe, guard, interceptor ve resolver'larda class adı son ekini koruyor ama dosya adında nokta yerine tire kullanılıyor: from-now-pipe.ts içinde FromNowPipe.
Mevcut projen bundan etkilenmiyor. ng update sırasında çalışan migration, angular.json içine eski adlandırmayı koruyan ayarları ekliyor. Tek bir component'i eski usulle üretmek istersen --type=component seçeneği de var. Rehberin diğer önerileri arasında sadece template'te kullanılan property'leri protected, input() ve output() gibi Angular'ın başlattığı alanları readonly yapmak var; yukarıdaki örneklerde bunu görebilirsin.
*ngIf, *ngFor ve *ngSwitch Deprecated
Blok A boyunca template'lerimiz şöyle görünüyordu:
<p *ngIf="tasks.length === 0">Görev yok</p>
<li *ngFor="let task of tasks; trackBy: trackById">{{ task.title }}</li>
Daha önce gördüğümüz yerleşik control flow ile karşılığı:
@for (task of tasks; track task.id) {
<li>{{ task.title }}</li>
} @empty {
<p>Görev yok</p>
}
Angular 20 ile NgIf, NgFor ve NgSwitch resmi olarak deprecated oldu. Deprecated, "kaldırıldı" demek değil; "ileride bir major sürümde kaldırılacak, şimdiden yeni yola geç" demek. Mevcut kodun çalışmaya devam ediyor, nitekim bu yazının yazıldığı Angular 22'de bile bu directive'ler hâlâ yerinde. ngTemplateOutlet gibi diğer structural directive'ler ise bu kararın dışında.
Elle tek tek çevirmene gerek yok, Angular bunun için bir migration sunuyor:
ng generate @angular/core:control-flow
Zoneless'ın Durumu
Angular 18'de deneysel olarak tanıştığımız zoneless change detection, Angular 20.0 ile developer preview'a geçti ve provider'ın adından "Experimental" kelimesi düştü:
bootstrapApplication(App, {
providers: [
provideZonelessChangeDetection(),
provideBrowserGlobalErrorListeners()
]
});
ng new artık zoneless isteyip istemediğini soruyor, bayrağın adı da --zoneless oldu. Hikâye burada bitmiyor: Ağustos 2025'te çıkan 20.2 ile zoneless stable oldu. Yani Angular 20'nin ömrü boyunca zoneless, "deneyebilirsin" seviyesinden "üretimde güvenle kullanabilirsin" seviyesine çıktı.
Template İfadelerinde Yenilikler
Template'ler de TypeScript'e biraz daha yaklaştı:
<p>{{ 2 ** 10 }}</p>
@if ('admin' in permissions) {
<a routerLink="/admin">Yönetim</a>
}
<button (click)="void save()">Kaydet</button>
<p>{{ `Merhaba ${name}` }}</p>
Üs alma için **, bir nesnede anahtar var mı diye bakmak için in ve fonksiyonun dönüş değerini yok saymak için void Angular 20'de geldi. void küçük ama işe yarar: event handler'dan yanlışlıkla false dönerse tarayıcının varsayılan davranışı engellenir, void bunun önüne geçer. Son satırdaki template literal desteği 19.2'de gelmişti; Angular 20 buna etiketli template literal'leri de ekledi.
Angular 20'ye Geçiş: ng update
ng update @angular/core@20 @angular/cli@20
Bu sürümde Node.js 18 desteği bitti. Angular 20 için ^20.19.0, ^22.12.0 ya da ^24.0.0 serilerinden biri gerekiyor. TypeScript tarafında en az 5.8 şart.
Güncelleme sonrası birkaç kırılmaya hazırlıklı ol: TestBed.get() ve InjectFlags kaldırıldı, DOCUMENT token'ı @angular/core paketine taşındı ve @angular/platform-browser-dynamic paketi deprecated oldu. Import değişikliklerinin çoğunu migration kendisi yapıyor. Kodunda resource kullandıysan request ve loader satırlarını ayrıca gözden geçir.
Küçük Bir Deneme
npx @angular/cli@20 new deneme ile yeni bir proje aç ve önce src/app klasörüne bak: app.module.ts yok, app.component.ts yerine app.ts var. Ardından ng g c user-profile çalıştır ve oluşan dosya ile class adlarını incele.
app.config.ts içine provideHttpClient() ekle. UserProfile component'ini yukarıdaki gibi yaz, URL olarak https://jsonplaceholder.typicode.com/users/${this.userId()} kullan. App içinde bir id signal'ı tut, iki butonla değerini artırıp azalt ve [userId]="id()" ile component'e gönder. Son olarak *ngIf ve *ngFor kullanan küçük bir template yazıp ng generate @angular/core:control-flow ile nasıl dönüştüğünü izle.
Projeyi açarken CLI sana zoneless isteyip istemediğini sordu; Angular 20'de bu hâlâ bir tercih. Sırada, zoneless'ın varsayılan olduğu, Signal Forms'un deneysel olarak geldiği ve Karma'nın yerini Vitest'in aldığı Angular 21 var.