Angular, modern web uygulama geliştirme dünyasında öncü bir çerçeve olarak kabul edilir. Kökenleri, 2010 yılında Google mühendisleri tarafından geliştirilen AngularJS adlı bir JavaScript kütüphanesine dayanır. AngularJS, dönemi için devrim niteliğinde olan iki yönlü veri bağlama (two-way data binding) gibi özellikleriyle hızlıca popülerlik kazandı.
Ancak, web teknolojilerinin hızla gelişmesi ve tek sayfa uygulamaların (Single Page Applications - SPA) daha karmaşık hale gelmesiyle, AngularJS'in mimari sınırlamaları belirginleşmeye başladı. Büyük ölçekli uygulamalarda performans sorunları, hiyerarşik bağımlılık enjeksiyonunun zorluğu ve bileşen tabanlı mimariye tam anlamıyla geçememesi gibi eksiklikler, Google'ı radikal bir karar almaya yöneltti. Bu karar, "Angular 2" olarak adlandırılan ve tamamen sıfırdan yazılmış yeni bir çerçevenin duyurulmasıydı. Bu geçiş, yalnızca bir sürüm yükseltmesi değil, farklı bir paradigmaydı.
Angular 2'nin 2016'daki nihai sürümüyle birlikte, TypeScript kullanımının zorunlu hale getirilmesi, bileşen tabanlı mimarinin benimsenmesi ve modüler yapının öne çıkması gibi köklü değişiklikler, AngularJS'den gelen geliştiriciler arasında önemli bir öğrenme eğrisi yarattı. Bu kopuş, "Angular" (2 ve üzeri) ile "AngularJS" (1.x) olarak iki farklı varlığın tanımlanmasına neden oldu. Google, ardından düzenli güncelleme stratejisiyle her altı ayda bir yeni bir sürüm yayınlayarak, çerçevenin modern web standartlarıyla senkronize kalmasını sağladı.
Temel Mimari: Bileşenler ve Modüller
Angular mimarisinin kalbinde, uygulamanın arayüzünü ve işlevselliğini oluşturan bileşenler (components) ve bu bileşenleri mantıksal gruplar halinde organize eden modüller (modules) yatar. Her Angular uygulaması, en az bir kök bileşen (AppComponent) ve bir kök modül (AppModule) ile başlar. Bu yapı, uygulamanın tüm parçalarının düzenli, yönetilebilir ve yeniden kullanılabilir olmasını sağlar.
Bir bileşen, üç temel parçadan oluşur: Bir TypeScript sınıfı (.ts dosyası), bu sınıfa ait iş mantığını ve verileri tanımlar; bir HTML şablonu (.html dosyası), kullanıcıya gösterilecek görünümü yapılandırır; ve bir CSS (veya Sass/Less) dosyası (.css/.scss), bileşene özel stil kurallarını içerir. Bu üçlü yapı, iş mantığı, sunum ve stil kavramlarını net bir şekilde ayırarak "Separation of Concerns" ilkesini somutlaştırır.
Bileşenler, ağaç benzeri bir hiyerarşi içinde iç içe yerleştirilebilir. Üst bileşen (parent), alt bileşene (child) veri geçirebilir ve alt bileşenin tetiklediği olaylara tepki verebilir. Bu, karmaşık kullanıcı arayüzlerinin, kendi kendini yöneten ve bağımsız birimler olan küçük bileşnlerden inşa edilmesine olanak tanır. Örneğin, bir alışveriş sitesindeki ürün kartı, sepete ekleme düğmesi ve kullanıcı yorumları, hepsi birbirinden ayrı bileşenler olarak geliştirilebilir.
Modüller, ilgili bileşenleri, direktifleri, servisleri ve boruları (pipes) bir araya getiren bir kapsayıcıdır. @NgModule dekoratörü ile tanımlanırlar ve declarations, imports, providers ve bootstrap gibi meta veri alanlarına sahiptir. Root Module (AppModule) uygulamanın giriş noktasıyken, Feature Module'lar belirli işlevsellik bloklarını (örneğin, `KullanıcıYönetimiModule`, `AdminPanelModule`) kapsar. Bu, uygulamayı mantıksal bölümlere ayırmanın ve Lazy Loading (tembel yükleme) gibi performans optimizasyonlarını uygulamanın temelini oluşturur.
Özetle, bileşenler uygulamanın yapı taşlarıyken, modüller bu taşların hangi araç kutularına yerleştirildiğini ve birbirleriyle nasıl etkileşime geçeceklerini tanımlayan plan görevi görür. Bu soyutlama, büyük ekiplerin farklı modüller üzerinde paralel çalışmasını mümkün kılar, kodun yeniden kullanımını artırır ve uyglamanın test edilebilirliğini önemli ölçüde iyileştirir. Angular'ın bu yapısal yaklaşımı, kurumsal ölçekteki projelerde tercih edilmesinin en temel nedenlerinden biridir.
Aşağıdaki tablo, Angular mimarisindeki temel yapı taşlarını ve işlevlerini özetlemektedir. Bu tablo, her bir unsurun mimari içindeki rolünü anlamak için faydalı bir başvuru kaynağıdır.
| Yapı Taşı | Açıklama | Temel Dekoratör / Anahtar |
|---|---|---|
| Bileşen (Component) | Kullanıcı arayüzünün bir bölümünü ve ilgili iş mantığını kontrol eden temel birim. | @Component |
| Modül (Module) | İlgili bileşen, servis ve diğer kod parçalarını düzenleyen kapsayıcı. | @NgModule |
| Servis (Service) | Veri erişimi, iş mantığı veya harici API iletişimi gibi uygulama genelindeki görevleri paylaşan sınıf. | @Injectable |
| Direktif (Directive) | DOM'un davranışını veya yapısını değiştiren sınıf. Bileşenler özel bir direktif türüdür. | @Directive |
| Boru (Pipe) | Şablondaki verileri dönüştürmek için kullanılan, yeniden kullanılabilir filtre. | @Pipe |
TypeScript ile Gelen Güç
Angular'ın temel dil seçimi, Microsoft tarafından geliştirilen ve JavaScript'in üst kümesi (superset) olan TypeScript'tir. Bu tercih, Angular'ı geliştiricilere güçlü tip sistemi, nesne yönelimli programlama (OOP) özellikleri ve gelişmiş araç desteği sunarak kurumsal geliştirme için ideal bir platform haline getirir.
TypeScript'in en önemli katkısı, derleme zamanında (compile-time) tip güvenliğini sağlamasıdır. Değişkenler, fonksiyon parametreleri ve dönüş değerleri için tanımlanan tipler (string, number, custom interface gibi), geliştiriciye hata yapma olasılığını büyük ölçüde azaltan bir güvenlik ağı sağlar. Bu, büyük kod tabanlarında, özellikle birden fazla geliştiricinin çalıştığı projelerde, çalışma zamanı hatalarını önler ve kodun güvenilirliğini artırır. IDE'ler (Visual Studio Code, WebStorm vb.) bu tip bilgilerini kullanarak otomatik tamamlama (IntelliSense), gezinme ve refactoring gibi gelişmiş özellikler sunar, bu da geliştirici verimliliğini ve kod kalitesini önemli ölçüde yükseltir.
TypeScript, sınıflar (classes), arayüzler (interfaces), genel tipler (generics) ve dekoratörler (decorators) gibi modern programlama konseptlerini destekler. Angular, dekoratörleri (@Component, @Injectable, @Input gibi) yoğun bir şekilde kullanarak bildirimsel (declarative) bir programlama modeli sunar. Bu meta veriler, Angular çalışma zamanına (runtime) sınıfların nasıl işleneceği ve birbirleriyle nasıl ilişkilendirileceği konusunda talimat verir.
| TypeScript Özelliği | Angular'daki Karşılığı / Faydası | Basit Kod Örneği |
|---|---|---|
| Tip Sistemi (Types) | Veri modellerinin, servis yanıtlarının ve bileşen giriş/çıkışlarının güvenli bir şekilde tanımlanması. | title: string;users: User[]; |
| Arabirimler (Interfaces) | Nesnelerin şeklini (shape) tanımlamak, sözleşmeler oluşturmak. | interface Product { id: number; name: string; } |
| Dekoratörler (Decorators) | Bileşen, modül, servis vb. Angular yapılarını meta verilerle donatmak. | @Component({...})export class MyComponent {} |
| Sınıflar (Classes) | Bileşenlerin, servislerin ve diğer enjekte edilebilir sınıfların temel yapı taşı. | export class DataService { ... } |
| Modüller (ES6 Modules) | Kod organizasyonu ve bağımlılık yönetimi (import/export). |
import { Component } from '@angular/core'; |
Veri Bağlama (Data Binding)
Angular'ın en güçlü özelliklerinden biri, bileşen sınıfı (TypeScript kodu) ile HTML şablonu arasında sorunsuz bir bağlantı sağlayan Veri Bağlama (Data Binding) mekanizmasıdır. Bu mekanizma, kullanıcı arayüzünün uygulama durumuna dinamik olarak tepki vrmesini ve kullanıcı etkileşimlerinin uygulama mantığına iletilmesini sağlar. Angular, dört farklı veri bağlama türü sunar.
Interpolasyon (String Interpolation): Bileşen sınıfındaki bir değeri şablonda metin olarak göstermenin en basit yoludur. Çift süslü parantez ({{ }}) içine yazılan ifade, bileşen sınıfındaki bir özellik veya metot çağrısına karşılık gelir ve bu değer HTML'de düz metin olarak işlenir. Örneğin, <h1>{{ pageTitle }}</h1> ifadesi, bileşen sınıfındaki `pageTitle` özelliğinin değerini başlık olarak gösterir.
Özellik Bağlama (Property Binding): DOM öğelerinin, yönergelerin veya bileşenlerin özelliklerini (properties) bileşen sınıfındaki değerlere bağlamak için kullanılır. Köşeli parantez ([ ]) sözdizimi ile ifade edilir. Bu tek yönlü bir bağlamadır: bileşenden DOM'a doğru. Örneğin, bir görselin kaynağını dinamik olarak ayarlamak için <img [src]="imageUrl"> veya bir düğmenin devre dışı bırakılması durumunu kontrol etmek için <button [disabled]="isSaving"> kullanılabilir.
Olay Bağlama (Event Binding): DOM olaylarını (klik, giriş değişikliği, fare hareketi vb.) bileşen sınıfındaki metotlara bağlar. Parantez (( )) sözdizimi ile ifade edilir. Bu, kullanıcıdan bileşene doğru bir bağlamadır. Örneğin, <button (click)="onSave()">Kaydet</button> ifadesi, düğmeye tıklandığında bileşen sınıfındaki `onSave()` metodunu çalıştırır.
İki Yönlü Veri Bağlama (Two-Way Data Binding): Form elemanları gibi hem giriş (input) hem de çıkış (output) gerektiren durumlar için idealdir. Bu bağlama, özellik bağlama ve olay bağlamasını birleştirerek çift yönlü bir veri akışı oluşturur. Kullanılan sözdizimi, köşeli parantez ve parantezin birleşimi olan [()]'dir ve genellikle ngModel yönergesi ile birlikte kullanılır.
- Interpolasyon:
{{ veri }}- Bileşenden şablona (Tek yönlü). - Özellik Bağlama:
[özellik]="veri"- Bileşenden hedef özelliğe (Tek yönlü). - Olay Bağlama:
(olay)="işleyici()"- Hedef olaydan bileşene (Tek yönlü). - İki Yönlü Bağlama:
[(ngModel)]="veri"- Bileşen ve hedef arasında çift yönlü.
| Bağlama Türü | Sözdizimi | Yön | Yaygın Kullanım |
|---|---|---|---|
| Interpolasyon | {{ expression }} |
Bileşen → DOM | Metin görüntüleme |
| Özellik Bağlama | [target]="expression" |
Bileşen → Hedef Özellik | HTML/Component/Directive özelliklerini ayarlama |
| Olay Bağlama | (target)="statement" |
Hedef Olay → Bileşen | Kullanıcı etkileşimlerini işleme |
| İki Yönlü Bağlama | [(ngModel)]="expression" |
Bileşen ↔ Hedef | Form girdilerini işleme |
Hizmetler (Services) ve Bağımlılık Enjeksiyonu (DI)
Angular mimarisinde, Hizmetler (Services) veri erişimi, mantık paylaşımı ve harici API iletişimi gibi belirli, odaklanmış görevleri yerine getiren sınıflardır. Bir servis, genellikle uygulama genelinde veya belirli bir modül içinde yeniden kullanılabilir ve paylaşılabilir işlevsellik sağlar. Örneğin, bir kullanıcı servisi kimlik doğrulama ve kullanıcı verilerini yönetirken, bir veri servisi HTTP üzerinden bir sunucuyla iletişim kurar.
Servislerin gücü, Bağımlılık Enjeksiyonu (Dependency Injection - DI) sistemiyle birleştiğinde ortaya çıkar. DI, Angular'ın en temel tasarım desenlerinden biridir ve bir sınıfın ihtiyaç duyduğu bağımlılıklarının (servisler veya diğer nesneler) dışarıdan, genellikle bir yapıcı (constructor) aracılığıyla sağlanması prensibine dayanır. Bu yaklaşım, sınıflar arasında sıkı bağlantıyı (tight coupling) azaltır ve kodun test edilebilirliğini, modülerliğini ve bakımını büyük ölçüde artırır.
Bir servis, @Injectable() dekoratörü ile işaretlenerek Angular'ın DI sistemine katılır. Bu dekoratördeki providedIn meta verisi, servisin nerede sağlanacağını belirler. En yaygın kullanım, providedIn: 'root' değeridir; bu, servisin uygulama kök seviyesinde (root injector) bir singleton (tekil örnek) olarak oluşturulacağını ve her yerde kullanılabileceğini gösterir. Alternatif olarak, bir servis yalnızca belirli bir modülde veya bileşende providers dizisi aracılığıyla sağlanabilir, bu da servisin kapsamını (scope) sınırlar.
// Servis Tanımı
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root' // Kök seviyesinde singleton olarak sağlanır.
})
export class ProductService {
constructor(private http: HttpClient) {} // DI ile HttpClient enjekte edilir.
getProducts() {
return this.http.get('api/products');
}
}
// Bileşen İçinde Kullanımı
import { Component } from '@angular/core';
import { ProductService } from './product.service';
@Component({...})
export class ProductListComponent {
products: any[];
constructor(private productService: ProductService) { // Servis DI ile enjekte edilir.
this.productService.getProducts().subscribe(data => this.products = data);
}
}
Angular'ın DI sistemi, hiyerarşik bir enjektör (injector) ağacı üzerinde çalışır. Her bileşen kendi enjektörüne sahip olabilir ve bir bağımlılık arandığında, Angular önce bileşenin enjektörüne bakar, ardından üst bileşenlerin enjektörlerine çıkar ve en sonunda kök enjektöre ulaşır. Bu hiyerarşi, örneğin, belirli bir bileşen ağacı için özelleştirilmiş bir servis örneği sağlamaya olanak tanır. Bağımlılık Enjeksiyonu olmadan, servislerin manuel olarak oluşturulması ve yönetilmesi gerekecek, bu da kodun esnekliğini ve test edilebilirliğini ciddi şekilde kısıtlayacaktı.
Yönlendirme (Routing) ve Lazy Loading
Angular Router modülü, tek sayfa uygulamalarında (SPA) görünümler arasında gezinmeyi yönetmek için güçlü bir çözüm sunar. Router, belirli bir URL'yi (route) belirli bir Angular bileşenine (view) eşler, böylece sayfanın tamamının yeniden yüklenmesine gerek kalmadan uygulamanın farklı bölümleri arasında geçiş yapılabilir. Bu, kullanıcı deneyimini hızlandırır ve uygulamaya doğal bir his kazandırır.
Router'ın temel yapılandırması, bir route (yol) dizisi tanımlamayı içerir. Her yol, bir URL yolu (path), bu yola gidildiğinde yüklenecek bileşen ve isteğe bağlı ek veriler (data) içerir. Yönlendirme, genellikle uygulamanın kök modülünde (AppModule) veya özel bir yönlendirme modülünde, RouterModule.forRoot(routes) metodu kullanılarak yapılandırılır. Şablonlarda, <router-outlet> direktifi, geçerli rotaya karşılık gelen bileşenin nerede işleneceğini belirten bir yer tutucu görevi görür. Gezinme, routerLink direktifi ile veya Router servisi aracılığıyla programatik olarak yapılır.
Büyük ölçekli uygulamalar için, Lazy Loading (Tembel Yükleme) kritik bir optimizasyon tekniğidir. Tembel yüklemeyle, bir özellik modülü (örneğin, `/admin` rotaları) ve ilişkili bileşenleri, kullanıcı o rotaya ilk kez gittiğinde dinamik olarak yüklenir. Bu, uygulamanın ilk yüklenme süresini (initial bundle size) önemli ölçüde azaltır, çünkü tüm uygulama kodu başlangıçta indirilmek yerine, ihtiyaç duyuldukça parçalar halinde yüklenir. Lazy loading, Angular'ın modüler yapısı sayesinde doğal ve kolay bir şekilde uygulanabilir.
Lazy loading yapılandırması için, öncelikle özellik modülü ayrı bir JavaScript dosyasına (chunk) ayrılmalıdır. Daha sonra, ana yönlendirme yapılandırmasında, ilgili yol (path) için bir `loadChildren` özelliği tanımlanır. Bu özellik, modülün yolunu ve modül sınıfının adını bir fonksiyon olarak belirtir (genellikle dinamik `import()` sözdizimi ile). Angular Router, bu rotaya gidildiğinde, bu fonksiyonu çağırır, modülü uzaktan yükler ve ardından modülün içindeki rotaları yönlendirme konfigürasyonuna enjekte eder.
// Ana Uygulama Yönlendirmesi (app-routing.module.ts)
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
const routes: Routes = [
{ path: '', component: HomeComponent },
{
path: 'products',
loadChildren: () => import('./products/products.module').then(m => m.ProductsModule) // Lazy Loading
},
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) // Lazy Loading
},
{ path: '**', component: PageNotFoundComponent } // Joker kart rotası (404)
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
// ProductsModule içindeki yönlendirme (products-routing.module.ts)
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductListComponent } from './product-list/product-list.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';
const routes: Routes = [
{ path: '', component: ProductListComponent }, // /products rotası buraya yönlendirilir
{ path: ':id', component: ProductDetailComponent } // /products/123
];
@NgModule({
imports: [RouterModule.forChild(routes)], // forChild kullanılır
exports: [RouterModule]
})
export class ProductsRoutingModule { }
Router, ayrıca rota parametrelerini (/products/:id), sorgu parametrelerini (/products?category=books), rota koruyucularını (guards - kimlik doğrulama kontrolü için) ve çözümleyicileri (resolvers - bileşen yüklenmeden önce veri getirmek için) destekler. Bu özellikler, karmaşık gezinme senaryolarını ve veri yönetimini sağlam bir şekilde ele almayı mümkün kılar.
Form İşleme
Angular'da kullanıcı girdilerini yönetmek için iki ana form yaklaşımı bulunur: Template-Driven Forms ve Reactive Forms. Her ikisi de form kontrollerini oluşturur, değerleri izler, geçerlilik durumunu (validation) kontrol eder ve hata mesajlarını gösterir, ancak felsefeleri ve karmaşıklık seviyeleri farklıdır. Doğru yaklaşımın seçimi, formun karmaşıklığına ve geliştirici tercihine bağlıdır.
Template-Driven Forms (Şablon Odaklı Formlar), mantığın büyük ölçüde HTML şablonunda tanımlandığı daha basit bir yaklaşımdır. Bu yaklaşım, AngularJS'deki form yönetimine benzer. Kullanımı için `FormsModule`'ün uygulamaya eklenmesi gerekir. Form kontrolleri, şablonda `ngModel` direktifi kullanılarak, genellikle iki yönlü bağlama ([(ngModel)]) ile bileşen sınıfındaki bir özelliğe bağlanır. Doğrulama kuralları, HTML5 özellikleri (`required`, `minlength`, `pattern` vb.) ve `ngModel`'den türetilen yerel değişkenler (`#name="ngModel"`) aracılığıyla eklenir. Bu yaklaşım, hızlı prototipleme ve basit formlar için idealdir.
Reactive Forms (Reaktif Formlar) ise daha açık ve tepkisel (reactive) bir programlama modeli sunar. Form mantığı tamamen bileşen sınıfı içinde, TypeScript kodu ile tanımlanır. Kullanımı için `ReactiveFormsModule`'ün eklenmesi gerekir. Bu yaklaşımda, form bir FormGroup nesnesi olarak oluşturulur ve bu grubun içindeki her bir kontrol (FormControl) bileşen sınıfında ayrı ayrı tanımlanır. Şablon, bu kontrolleri [formGroup] ve formControlName direktifleriyle bağlar. Doğrulama, senkron veya asenkron doğrulayıcı fonksiyonların (Validators.required, custom validator) kontrolün yapıcısına parametre olarak geçirilmesiyle yapılır. Reactive Forms, daha fazla kontrol, daha iyi test edilebilirlik ve karmaşık dinamik form senaryoları için tercih edilir.
// REACTIVE FORM Örneği (Bileşen Sınıfı)
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-user-form',
templateUrl: './user-form.component.html'
})
export class UserFormComponent {
userForm: FormGroup;
constructor(private fb: FormBuilder) {
this.userForm = this.fb.group({
name: ['', [Validators.required, Validators.minLength(2)]],
email: ['', [Validators.required, Validators.email]],
age: ['', [Validators.min(18)]]
});
}
onSubmit() {
if (this.userForm.valid) {
console.log(this.userForm.value);
}
}
}
Reactive Forms, form değerlerindeki değişiklikleri gözlemlemek için valueChanges ve statusChanges gibi gözlemlenebilir (observable) akışlar sağlar. Bu, RxJS operatörleriyle (debounceTime, distinctUntilChanged, switchMap) birleştirilerek, kullanıcı girdisi sırasında anında doğrulama veya otomatik tamamlama gibi gelişmiş senaryoları uygulamayı mümkün kılar. Her iki yaklaşım da Angular ekosisteminin güçlü parçalarıdır ve projenin ihtiyaçlarına göre seçim yapılabilir.
HTTP İstemcisi ve HttpClient Modülü
Modern web uygulamaları, sunucu ile sürekli veri alışverişi yapar. Angular, bu işlem için @angular/common/http paketinde bulunan, güçlü ve kullanımı kolay HttpClient servisini sağlar. HttpClient, XMLHttpRequest API'si üzerine inşa edilmiş, ancak gözlemlenebilir (observable) tabanlı, tip güvenliği olan ve test edilebilirliği yüksek bir soyutlamadır. Temel HTTP istek metotlarını (GET, POST, PUT, DELETE, PATCH) destekler ve istek/cevap işlemlerini yapılandırmak için kapsamlı seçenekler sunar.
HttpClient'i kullanmak için öncelikle HttpClientModule'ün uygulama veya özellik modülüne (imports dizisine) eklenmesi gerekir. Daha sonra, HTTP istekleri göndermek istediğiniz servis veya bileşene, Bağımlılık Enjeksiyonu (DI) yoluyla HttpClient servisi enjekte edilir. Bu servisin metotları (örneğin, http.get(), http.post()), tip parametresi alabilen ve bir RxJS Observable döndüren fonksiyonlardır. Bu observable'ı subscribe etmek, HTTP isteğini tetikler ve yanıt veya hata durumunda bildirim almayı sağlar.
HttpClient'in en önemli avantajlarından biri, gelen yanıtları (response) otomatik olarak JSON formatından JavaScript nesnelerine dönüştürmesidir (eğer yanıt tipi uygunsa). Ayrıca, istek gövdelerini (request body) de JSON'a çevirir. Tip güvenliği sağlamak için, metotlara bir tip parametresi (http.get<Product[]>(...)) verilebilir, bu da dönen observable'ın akışının ve subscribe işleminin sonucunun belirtilen tipte olacağını garanti eder ve geliştirici deneyimini iyileştirir.
// HttpClient ile Tip Güvenliği
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
// Yanıt tipini tanımlayan Interface
interface User {
id: number;
name: string;
email: string;
}
@Injectable({ providedIn: 'root' })
export class UserService {
private apiUrl = 'https://api.example.com/users';
constructor(private http: HttpClient) {}
// GET isteği. Dönüş tipi Observable olarak belirlendi.
getUsers(): Observable {
return this.http.get(this.apiUrl);
}
// POST isteği. Gönderilen ve alınan verinin tipi belirlendi.
createUser(user: User): Observable {
return this.http.post(this.apiUrl, user);
}
// Hata işleme ve Interceptor kullanımı için örnek
getUserById(id: number): Observable {
return this.http.get(`${this.apiUrl}/${id}`);
// Hatalar, subscribe içinde veya catchError operatörü ile yakalanır.
}
}
Angular HTTP istekleri, Interceptor'lar aracılığıyla merkezi olarak yönetilebilir ve dönüştürülebilir. Bir interceptor, giden her istek ve gelen her yanıt için bir aracı (middleware) görevi görür. Interceptor'lar, kimlik doğrulama token'larını eklemek, hata kodlarını işlemek, istekleri loglamak veya global yükleniyor (loading) durumu göstermek gibi yaygın görevler için idealdir. Ayrıca, `HttpClient` CORS (Cross-Origin Resource Sharing) ile güvenli bir şekilde çalışır ve sunucu tarafından dönen uygun başlıklara (headers) ihtiyaç duyar. Test aşamasında, `HttpClientTestingModule` kullanılarak HTTP istekleri kolayca taklit edilebilir (mock) ve birim testleri yazılabilir.
RxJS ve Reaktif Programlama
Angular'ın tepkisel (reactive) ve asenkron programlama modelinin temelinde, RxJS (Reactive Extensions for JavaScript) kütüphanesi yatar. RxJS, gözlemlenebilirler (Observables), gözlemciler (Observers) ve operatörler (Operators) kullanarak veri akışlarını (streams) ve asenkron olayları yönetmek için güçlü bir araç seti sağlar. Angular'ın kendi içindeki birçok API (HttpClient, Form değer değişiklikleri, Router olayları) RxJS Observable'ları döndürür, bu da Angular'ı öğrenirken RxJS kavramlarını anlamayı zorunlu kılar.
Bir Observable, zaman içinde yayılabilecek (emit) sıfır veya daha fazla değerin temsilidir. Bu, bir dizi (array) veya bir Promise'ten farklıdır; çünkü Observable, değerleri zamanla "iterek" (push) tüketiciye iletir ve potansiyel olarak sonsuz sayıda değer yayabilir. Bir Observable'a subscribe() metodu ile abone olunur; bu, Observable'ın yaydığı değerleri işleyecek bir Observer (genellikle bir nesne veya fonksiyon) sağlar. Observer, next() (bir sonraki değer), error() (bir hata) ve complete() (akışın tamamlanması) bildirimlerini alabilir.
RxJS'nin gerçek gücü, veri akışlarını dönüştürmek, birleştirmek, filtrelemek ve yönetmek için kullanılan yüzlerce operatörden gelir. Bu operatörler, fonksiyonel programlama tarzında zincirlenebilir (pipeable) ve Observable kaynağını değiştirmeden yeni bir Observable döndürür. Örneğin, map() operatörü akıştaki her değeri dönüştürür, filter() belirli kriterlere uyan değerleri geçirir, debounceTime() kullanıcı girdisi gibi hızlı değişen olayları yönetir ve switchMap() bir iç Observable'a geçiş yaparken önceki iç istekleri iptal edebilir.
// RxJS Operatörleri ile Asenkron Akış Yönetimi
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { debounceTime, distinctUntilChanged, switchMap, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
@Component({
selector: 'app-search',
template: ``
})
export class SearchComponent implements OnInit {
searchControl = new FormControl('');
constructor(private http: HttpClient) {}
ngOnInit() {
this.searchControl.valueChanges
.pipe(
debounceTime(300), // Kullanıcı yazmayı durdurduktan 300ms sonra
distinctUntilChanged(), // Değer öncekiyle aynı değilse
switchMap(term => { // Yeni arama terimi geldiğinde önceki isteği iptal et
if (term) {
return this.http.get(`/api/search?q=${term}`).pipe(
catchError(error => of([])) // Hata durumunda boş dizi döndür
);
} else {
return of([]); // Terim boşsa boş dizi döndür
}
})
)
.subscribe(results => {
console.log('Arama Sonuçları:', results);
// Sonuçları görüntüle
});
}
}
switchMap, RxJS'nin Angular uygulamalarında en kritik operatörlerinden biridir. Yukarıdaki örnekte olduğu gibi, kullanıcı arama kutusuna yazarken, her tuş vuruşu potansiyel bir HTTP isteği anlamına gelir. switchMap, yeni bir değer (arama terimi) geldiğinde, önceki iç Observable'ın (önceki HTTP isteği) aboneliğini iptal eder ve yeni bir istek başlatır. Bu, "en güncel isteğin sonucunu al" mantığını uygular ve eski, artık geçersiz olan isteklerin tamamlanmasını bekleyerek kaynak israfını ve yarış koşullarını (race conditions) önler.
| RxJS Kavramı / Operatör | Açıklama | Angular'daki Yaygın Kullanımı |
|---|---|---|
| Observable | Zamanla yayılan değerlerin temsili (lazy, çoklu değer). | HTTP yanıtları, form değer değişiklikleri, router olayları. |
| Observer / Subscribe | Observable'dan gelen bildirimleri (next, error, complete) dinleyen ve işleyen yapı. | HTTP isteğinden gelen veriyi almak için .subscribe(). |
| Subject | Hem Observable hem Observer olabilen, çok noktaya yayın (multicast) yapabilen özel bir tip. | Bileşenler arası iletişim, global durum yönetimi (basit senaryolarda). |
| pipe(), map(), filter() | Akışı dönüştüren ve filtreleyen temel operatörler. | HTTP yanıt verisini dönüştürmek, istenmeyen değerleri elemek. |
| debounceTime(), distinctUntilChanged() | Kullanıcı etkileşimlerini optimize eden operatörler. | Arama kutuları (type-ahead), form validasyonu. |
| switchMap(), mergeMap(), concatMap() | Düzleştirme (flattening) operatörleri. Bir akışı başka bir iç akışa bağlar. | Sıralı/Paralel HTTP istekleri, arama iptali. |
| catchError() | Observable akışındaki hataları yakalar ve işler. | HTTP hatalarını merkezi olarak yönetmek. |
RxJS ve reaktif programlama paradigmasını benimsemek, Angular geliştiricilerine karmaşık asenkron senaryoları zarif ve açık bir şekilde yönetme gücü verir. Başlangıçta öğrenme eğrisi dik olsa da, bu model, veri akışını net bir şkilde görselleştirmeyi, yan etkileri kontrol etmeyi ve bileşenlerin durum değişikliklerine tepki vermesini sağlamayı kolaylaştırır. Angular'ın bu yaklaşımı, uygulamanın veri akışını daha öngörülebilir ve hata ayıklaması daha kolay hale getirir.
Angular CLI ve Geliştirici Deneyimi
Angular ekosisteminin en değerli araçlarından biri, komut satırı arayüzü olan Angular CLI'dır. CLI, bir Angular projesinin oluşturulmasından, geliştirilmesine, test edilmesine ve dağıtılmasına kadar tüm yaşam döngüsünü yönetmek için kapsamlı bir komut seti sağlar. @angular/cli paketi global olarak yüklenir ve ng komutu ile kullanılır. CLI, geliştiricileri karmaşık yapılandırma dosyaları (Webpack, TypeScript, Karma) ile uğraşmaktan kurtarır, tutarlı bir proje yapısı sunar ve geliştirici verimliliğini büyük ölçüde artırır.
ng new <project-name> komutu, tüm gerekli bağımlılıkları, konfigürasyon dosyalarını ve temel bir uygulama iskeletini içeren yepyeni bir Angular projesi oluşturur. CLI, proje oluşturma sırasında Routing ve SCSS/Less gibi stil formatı seçenekleri sunar. Proje oluşturulduktan sonra, ng generate (veya kısaca ng g) komutu, blueprint'ler (şablonlar) kullanarak uygulama için tutarlı ve doğru şekilde yapılandırılmış dosyalar oluşturur. Bu, bileşenler, servisler, direktifler, modüller ve daha fazlası için geçerlidir. Örneğin, ng g component products/product-list komutu, belirtilen yolda (products/) dört ayrı dosyadan (.ts, .html, .css, .spec.ts) oluşan bir bileşen oluşturur ve bunu ilgili modüle otomatik olarak declare eder.
Geliştirme sürecinde, ng serve komutu yerel bir geliştirme sunucusu başlatır, uygulamayı derler ve varsayılan olarak http://localhost:4200 adresinde sunar. Bu sunucu, Hot Module Replacement (HMR) benzeri bir özellik olan canlı yeniden yükleme (live reload) ile birlikte gelir; kaynak dosyalarda yapılan herhangi bir değişiklik tarayıcıda anında görüntülenir, bu da geliştirme hızını maksimuma çıkarır. CLI ayrıca, ng build ile uygulamayı üretim (production) için optimize edilmiş bir şekilde derler (Ahead-of-Time - AoT derleme, minifikasyon, paketleme), ng test ile birim testleri çalıştırır (Karma/Jasmine kullanarak) ve ng e2e ile uygulamayı uçtan uca (end-to-end) test eder (Protractor veya Cypress kullanarak).
ng new my-app: Yeni bir Angular uygulaması oluşturur.ng generate component my-component: Yeni bir bileşen oluşturur.ng generate service api: Yeni bir servis oluşturur.ng serve --open: Uygulamayı geliştirme sunucusunda çalıştırıp tarayıcıda açar.ng build --prod: Uygulamayı üretim ortamı için dağıtıma hazır derler.ng test: Birim testleri çalıştırır.ng add @angular/pwa: PWA (Progressive Web App) özelliklerini projeye ekler.ng update: Angular paketlerini ve bağımlılıklarını günceller.
Angular CLI, ekosistemi genişletmek için schematics adı verilen bir kod oluşturma altyapısı kullanır. Üçüncü taraf kütüphaneler, kendi schematic'lerini sağlayarak ng add komutuyla kütüphaneyi ve gerekli yapılandırmaları otomatik olarak kurup ayarlayabilir. Benzer şekilde, ng update komutu, paket sürümlerini güncellemenin yanı sıra, yeni sürümdeki değişikliklere uyum sağlamak için gerekli kod dönüşümlerini (migrations) otomatik olarak uygular. Bu araçlar, Angular projelerinin bakım maliyetini düşürür ve geliştiricilerin en son özellikler ve güvenlik düzeltmeleri ile senkronize kalmasını kolaylaştırır.
Performans ve Optimizasyon
Kurumsal ölçekteki Angular uygulamalarında performans, kullanıcı deneyimini ve başarıyı doğrudan etkileyen kritik bir faktördür. Angular, performansı artırmak için hem çalışma zamanında (runtime) hem de derleme aşamasında (build time) çeşitli teknikler ve araçlar sunar. Bu optimizasyonların bilinçli olarak uygulanması, uygulamanın hızını ve verimliliğini önemli ölçüde artırabilir.
İlk ve en önemli optimizasyon, Change Detection (Değişiklik Algılama) stratejisinin yönetilmesidir. Angular, varsayılan olarak, her asenkron olaydan sonra (örneğin, bir `click` olayı, bir timer veya bir HTTP isteğinin tamamlanması) uygulama durumunun değişip değişmediğini kontrol etmek için tüm bileşen ağacını tarar. Bu, ChangeDetectionStrategy.Default stratejisidir. Ancak, bileşenin girdileri (@Input) yalnızca immutable (değiştirilemez) nesneler ise, bileşenin değişiklik algılama stratejisini ChangeDetectionStrategy.OnPush olarak ayarlamak büyük bir performans kazancı sağlar. Bu stratejide, Angular, bir bileşenin girdi referansları değişmediği veya bileşen kendisi bir olay tetiklemediği sürece o bileşeni ve alt bileşenlerini kontrol etmez.
Performansı etkileyen bir diğer faktör, bileşen yaşam döngüsü (lifecycle) içindeki ağır hesaplamalardır. @Input() dekoratörlü bir setter kullanmak veya ngOnChanges yaşam döngüsü kancasında karmaşık mantık çalıştırmak, her değişiklik algılama döngüsünde tekrarlanabilir. Bu durumda, pure pipe'lar veya memoisasyon teknikleri kullanmak faydalı olabilir. Daha da iyisi, OnPush stratejisi ile birlikte immutable veri akışı kullanmak, Angular'ın gereksiz denetimleri atlamasına ve yalnızca gerçekten değişen bileşenleri güncellemesine olanak tanır.
Uygulamanın başlangıç yükünü (initial bundle size) azaltmak, ilk içerik görüntüleme süresini (First Contentful Paint - FCP) iyileştirmenin anahtarıdır. Bu konuda en etkili teknik, daha önce bahsedilen Lazy Loading'dir. Router aracılığıyla modülleri tembel yüklemek, kullanıcının o anda ihtiyaç duymadığı özelliklerin JavaScript kodunun ilk yüklemede indirilmesini engeller. Ek olarak, ng build --prod komutu, Ahead-of-Time (AoT) Derleme, Uglification, Tree Shaking ve Dead Code Elimination gibi bir dizi optimizasyonu otomatik olarak uygular. AoT derleme, şablonları çalışma zamanında değil, derleme sırasında TypeScript'e çevirerek tarayıcıdaki derleyiciyi kaldırır ve uygulamanın daha hızlı açılmasını sağlar.
// OnPush Change Detection ve Immutable Input Örneği
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-user-list',
template: `
- {{ user.name }}
`,
changeDetection: ChangeDetectionStrategy.OnPush // Sadece input değişirse kontrol et
})
export class UserListComponent {
// Input'un değişmediğinden emin olmak için, ana bileşenden her seferinde
// YENİ bir dizi referansı gelmeli (immutable update).
@Input() users: User[] = [];
}
// Ana bileşende immutable güncelleme:
// this.users = [...this.users, newUser]; // ES6 spread ile YENİ dizi
// YANLIŞ: this.users.push(newUser); // Aynı referans, OnPush ile güncellenmez.
Tree Shaking, kullanılmayan kodu nihai paketlerden (bundles) otomatik olarak kaldıran bir süreçtir ve Angular CLI'nın üretim derlemesinin bir parçasıdır. Bunun etkili çalışması için, kütüphanelerin ES2015 modül formatını (import/export) desteklemesi gerekir. Ayrıca, async pipe kullanımı, RxJS Observable'lara olan abonelikleri otomatik olarak yönetir ve bileşen yok edildiğinde bellek sızıntılarını önler. Manuel subscribe çağrılarında, bileşenin ngOnDestroy yaşam döngüsü kancasında abonelikleri sonlandırmak (unsubscribe()) zorunludur.
Performans izleme ve hata ayıklama için, Angular Angular DevTools adlı bir tarayıcı eklentisi sağlar. Bu eklenti, bileşen ağacını görselleştirmenize, değişiklik algılamayı profillemenize ve performans darboğazlarını belirlemenize olanak tanır. Düzenli profil analizi, gerçek kullanıcı etkileşimlerindeki yavaş noktaları tespit etmek ve optimizasyon çabalarını yönlendirmek için vazgeçilmezdir. Sonuç olarak, Angular, yüksek performanslı uygulamalar oluşturmak için gerekli tüm araçları ve teknikleri sunar; ancak bu potansiyeli gerçekleştirmek, geliştiricinin bu özellikleri doğru şekilde anlamasına ve uygulamasına bağlıdır.