Takip et

Angular Signals Nedir ve Uygulamada Nasıl Kullanılır? (Türkçe Rehber)

Angular uygulamalarınızda performans ve reaktiviteyi kökten değiştiren Signals yapısını merak mı ediyorsunuz? Bu kapsamlı rehberde, Angular Signals’ın ne olduğunu, nasıl çalıştığını ve gerçek dünya örnekleriyle projelerinizde nasıl kullanabileceğinizi adım adım keşfedin. Artık daha temiz, daha hızlı ve daha yönetilebilir kod yazmaya hazır olun!

Modern web uygulamaları sürekli etkileşim halindedir. Kullanıcılar veriyi girer, butonlara tıklar, listeleri kaydırır ve uygulamanın anında tepki vermesini bekler. İşte tam da bu noktada reaktivite devreye girer. Reaktivite, bir veri değiştiğinde, bu veriye bağlı olan tüm bileşenlerin otomatik ve verimli bir şekilde güncellenmesi yeteneğidir. Angular gibi tek sayfa uygulaması (SPA) çerçeveleri için bu, kullanıcı deneyimini doğrudan etkileyen kritik bir özelliktir.

Peki, Angular bu reaktiviteyi daha önce nasıl sağlıyordu? Temel olarak, Angular’ın değişim algılama mekanizması (change detection) devreye giriyordu. Bu mekanizma, bileşen ağacındaki değişiklikleri tespit etmek ve DOM’u buna göre güncellemek için tasarlanmıştır. Geleneksel olarak, Angular bunu Zone.js adı verilen bir kütüphane aracılığıyla yapmıştır. Zone.js, asenkron işlemler (HTTP istekleri, zamanlayıcılar, kullanıcı etkileşimleri) tetiklendiğinde Angular’ı bilgilendirerek tüm uygulama genelinde bir değişim algılama döngüsü başlatır. Bu yaklaşım, birçok durumda oldukça iyi çalışsa da, büyük ve karmaşık uygulamalarda performans darboğazlarına yol açabilir.

Örneğin, bir uygulamanın yüzlerce bileşeni olduğunu ve her bir kullanıcının her etkileşiminde tüm bileşen ağacının kontrol edildiğini düşünün. Her ne kadar Angular OnPush değişim algılama stratejisi gibi optimizasyonlar sunsa da, Zone.js’in doğası gereği geniş kapsamlı kontroller, gereksiz yeniden render’lara ve dolayısıyla yavaşlamalara neden olabilir. Geliştiriciler bu sorunları çözmek için RxJS gibi güçlü reaktif programlama kütüphanelerini kullanmak zorunda kalmıştır. RxJS, karmaşık asenkron veri akışlarını yönetmek için harika bir araç olsa da, kendi öğrenme eğrisi ve zengin operatör seti ile gelir. Bu durum, özellikle yeni başlayanlar için bazen kafa karıştırıcı olabilir ve kodun okunabilirliğini düşürebilir.

İşte tam da bu ihtiyaçlar doğrultusunda Angular Signals, daha modern, daha verimli ve daha sezgisel bir reaktivite modeli sunarak sahneye çıktı. Signals, Zone.js’in tüm uygulamayı kontrol etme yaklaşımının aksine, sadece belirli bir sinyale abone olan bileşenlerin veya DOM parçalarının güncellenmesini sağlar. Bu, çok daha ince taneli bir değişim algılama demektir. Veri değiştiğinde sadece etkilenen kısımlar güncellendiği için, uygulama performansı önemli ölçüde artar ve geliştiriciler için kod yazma deneyimi basitleşir. Kısacası, Angular Signals, uygulamalarımızın daha hızlı, daha akıcı ve daha duyarlı olmasını sağlayarak hem kullanıcıları hem de geliştiricileri mutlu etmeyi hedefler.

Angular Signals Temel Kavramları Nelerdir ve Nasıl Çalışır?

Angular Signals, reaktif programlamaya yeni bir bakış açısı getiriyor. Temelde, bir “signal” (sinyal), değeri değiştiğinde otomatik olarak bağımlı olduğu tüm yerleri bilgilendiren bir değer tutucudur. Bu, bir yayılma (propagation) mekanizması aracılığıyla gerçekleşir. RxJS’in aksine, Signals doğrudan değerleri içerir ve bunlara erişmek için .value veya subscribe() gibi yöntemlere ihtiyaç duymazsınız; doğrudan bir fonksiyon çağrısı gibi erişirsiniz (örn. mySignal()). Bu, özellikle şablonlarınızda (templates) kodun daha basit ve okunabilir olmasını sağlar.

Angular Signals’ın kalbinde üç ana kavram bulunur: signal(), computed() ve effect(). Bu üç yapı taşı, reaktif uygulamanızı inşa etmenizi sağlar.

  • signal(): Temel Reaktif Değerler
    signal() fonksiyonu, reaktif bir değer yaratmanın en temel yoludur. İçine başlangıç değeri alarak bir WritableSignal nesnesi döndürür. Bu nesne, mevcut değerine erişmek için bir getter fonksiyonu ve değerini değiştirmek için set() ve update() metotları sağlar. Bir sinyalin değeri değiştiğinde, bu sinyali okuyan (bağımlı olan) tüm computed veya effect blokları otomatik olarak yeniden çalışır.

    
            import { signal } from '@angular/core';
    
            const count = signal(0); // Başlangıç değeri 0 olan bir sinyal
            console.log(count());   // 0 - Değere erişim
    
            count.set(5);           // Değeri doğrudan 5 olarak ayarlar
            console.log(count());   // 5
    
            count.update(value => value + 1); // Mevcut değeri kullanarak günceller
            console.log(count());   // 6
          


    set() metodu, sinyalin değerini doğrudan yeni bir değere ayarlar. update() metodu ise, mevcut değeri alıp yeni bir değer döndüren bir geri arama fonksiyonu (callback function) kabul eder. Bu, mevcut değere bağlı olarak güncelleme yapmanız gerektiğinde oldukça kullanışlıdır, özellikle nesneler veya dizilerle çalışırken immutability (değişmezlik) prensibini korumanıza yardımcı olur.

  • computed(): Türetilmiş (Derived) Sinyaller
    computed() fonksiyonu, bir veya daha fazla sinyalden türetilmiş yeni bir sinyal oluşturmanızı sağlar. Bu, bir sinyalin değerinin başka sinyallerin değerlerine göre hesaplandığı durumlarda kullanılır. computed sinyaller, bağımlı oldukları sinyallerden herhangi biri değiştiğinde otomatik olarak yeniden hesaplanır. En önemlisi, computed sinyaller "memoize" edilir; yani, bağımlı oldukları sinyaller değişmediği sürece, tekrar tekrar çağrılsa bile hesaplama fonksiyonu yeniden çalıştırılmaz. Bu, ciddi performans iyileştirmeleri sağlar.

    
            import { signal, computed } from '@angular/core';
    
            const firstName = signal('Can');
            const lastName = signal('Yılmaz');
    
            const fullName = computed(() => ${firstName()} ${lastName()});
            console.log(fullName()); // "Can Yılmaz"
    
            firstName.set('Ayşe');
            console.log(fullName()); // "Ayşe Yılmaz" (otomatik olarak güncellendi)
          


    Bu örnekte, fullName sinyali, firstName veya lastName sinyallerinden herhangi biri değiştiğinde otomatik olarak güncellenir. computed'in içindeki fonksiyon, sadece bağımlı sinyallerin değerleri değiştiğinde tetiklenir, bu da verimli bir reaktivite sağlar.

  • effect(): Yan Etkileri Yönetme
    effect() fonksiyonu, sinyal değerlerine tepki olarak "yan etkileri" çalıştırmanıza olanak tanır. Yan etkiler, bir sinyalin değeri değiştiğinde tetiklenen kod parçalarıdır ve genellikle DOM manipülasyonu, konsola çıktı yazma, veritabanı kaydetme veya API çağrıları gibi şeyleri içerir. effect() fonksiyonu, bağımlı olduğu sinyallerden herhangi biri değiştiğinde otomatik olarak yeniden çalışır.

    
            import { signal, effect } from '@angular/core';
    
            const userId = signal(1);
    
            effect(() => {
              console.log(Kullanıcı ID değişti: ${userId()});
              // userId() değerini kullanarak bir API çağrısı yapabilir veya localStorage'a kaydedebilirsiniz.
            });
    
            userId.set(2); // Konsola "Kullanıcı ID değişti: 2" yazdırır
          

    Bir effect bloğu, bağımlı olduğu sinyallerin ilk okunduğu anda otomatik olarak çalışır ve daha sonra bu sinyaller her değiştiğinde tekrar çalışır. effect'in içindeki kod, Angular'ın değişim algılama döngüsünden bağımsız olarak tetiklenebilir ve bu da performansı optimize etmede büyük bir rol oynar. Ancak, effect'leri dikkatli kullanmak önemlidir; sonsuz döngüleri önlemek veya gereksiz yan etkileri tetiklememek için sadece gerçekten ihtiyaç duyulan yerlerde kullanılmalıdırlar.

Signals, Angular'ın Zone.js'e olan bağımlılığını azaltma ve gelecekte tamamen ortadan kaldırma potansiyeline sahiptir. Bu da daha küçük uygulama boyutları, daha hızlı başlangıç süreleri ve genel olarak daha iyi bir performans anlamına gelir. Geliştirici deneyimi açısından ise, kodun daha az "sihirli" hissettirmesi, reaktif akışın daha kolay takip edilebilir olması ve karmaşık RxJS operatörlerine olan ihtiyacın azalması gibi avantajlar sunar.

Angular Signals ile Basit Bir Sayaç Uygulaması Nasıl Yapılır?

Angular Signals'ın nasıl çalıştığını anlamanın en iyi yollarından biri, basit bir örnek üzerinde adım adım ilerlemektir. Şimdi, bir sayaç uygulaması üzerinden signal() ve computed() kavramlarını uygulamalı olarak görelim. Bu örnek,Signals'ın temel kullanımını ve nasıl reaktif değerler oluşturup güncelleyebileceğimizi anlamamızı sağlayacak.

Adım 1: Temel signal() Kullanımı

İlk olarak, sayacımızın mevcut değerini tutacak bir signal oluşturacağız. Ardından, bu değeri şablonumuzda gösterecek ve bir buton aracılığıyla değerini artırıp azaltabilecek bir bileşen tasarlayacağız.


    // counter.component.ts
    import { Component, signal } from '@angular/core';
    import { CommonModule } from '@angular/common'; // *ngIf, *ngFor gibi direktifler için

    @Component({
      selector: 'app-counter',
      template: 

Basit Sayaç Uygulaması

Mevcut Değer: {{ count() }}

Değer çift sayı.

Değer tek sayı.

, styles: [div { margin-top: 10px; } button { margin-right: 8px; padding: 10px 15px; border: none; border-radius: 5px; cursor: pointer; } button:first-of-type { background-color: #4CAF50; color: white; } button:nth-of-type(2) { background-color: #f44336; color: white; } button:last-of-type { background-color: #008CBA; color: white; } p { margin-top: 10px; font-style: italic; }], standalone: true, // Angular 15+ ile standalone bileşenler kullanabiliriz imports: [CommonModule] }) export class CounterComponent { // Başlangıç değeri 0 olan bir sinyal oluşturuyoruz. // TypeScript'te signal(0) otomatik olarak WritableSignal tipini çıkarır. count = signal(0); // Sayacı bir artırmak için kullanılan metot increment() { // update metodunu kullanarak mevcut değeri alıp 1 ekleyerek güncelliyoruz. this.count.update(currentValue => currentValue + 1); console.log('Sayaç artırıldı, yeni değer:', this.count()); } // Sayacı bir azaltmak için kullanılan metot decrement() { // update metodunu kullanarak mevcut değeri alıp 1 çıkararak güncelliyoruz. this.count.update(currentValue => currentValue - 1); console.log('Sayaç azaltıldı, yeni değer:', this.count()); } // Sayacı başlangıç değerine (0) sıfırlamak için kullanılan metot reset() { // set metodunu kullanarak sinyalin değerini doğrudan 0'a ayarlıyoruz. this.count.set(0); console.log('Sayaç sıfırlandı, yeni değer:', this.count()); } }

Yukarıdaki örnekte, count bir sinyal olduğu için şablonda count() şeklinde çağrılarak değerine erişilir. Bir butona tıklandığında increment(), decrement() veya reset() metotları çağrılır ve bu metotlar count sinyalinin değerini günceller. Angular, count sinyalindeki bu değişiklikleri otomatik olarak algılar ve sadece count() değerini kullanan p etiketindeki metni ve koşullu *ngIf bloklarını yeniden render eder. Bu, Zone.js'in tüm bileşen ağacını kontrol etmesinden çok daha verimlidir.

Adım 2: computed() ile Türetilmiş Değerler Ekleme

Şimdi, sayacımızın değerinden türetilmiş başka bir reaktif değer ekleyelim. Örneğin, sayacın çift mi tek mi olduğunu gösteren veya sayacın iki katını hesaplayan bir değer. Bunu computed() ile yapacağız.


    // computed-counter.component.ts
    import { Component, signal, computed } from '@angular/core';
    import { CommonModule } from '@angular/common';

    @Component({
      selector: 'app-computed-counter',
      template: 

Computed Sinyalleri ile Sayaç

Mevcut Değer: {{ count() }}

İki Katı: {{ doubleCount() }}

Tek mi Çift mi?: {{ parityStatus() }}

, styles: [div { margin-top: 10px; } button { margin-right: 8px; padding: 10px 15px; border: none; border-radius: 5px; cursor: pointer; } button:first-of-type { background-color: #4CAF50; color: white; } button:nth-of-type(2) { background-color: #f44336; color: white; } button:last-of-type { background-color: #008CBA; color: white; }], standalone: true, imports: [CommonModule] }) export class ComputedCounterComponent { count = signal(0); // Temel sayac sinyali // doubleCount bir computed sinyalidir. // count sinyali her değiştiğinde bu değer yeniden hesaplanır. // Ancak count değişmediği sürece, doubleCount() tekrar çağrılsa bile hesaplama tekrarlanmaz (memoization). doubleCount = computed(() => { console.log('doubleCount hesaplanıyor...'); // Sadece count değiştiğinde çalışır return this.count() * 2; }); // parityStatus da bir computed sinyalidir. // Sayının tek mi çift mi olduğunu belirler. parityStatus = computed(() => { console.log('parityStatus hesaplanıyor...'); // Sadece count değiştiğinde çalışır return this.count() % 2 === 0 ? 'Çift' : 'Tek'; }); increment() { this.count.update(currentValue => currentValue + 1); } decrement() { this.count.update(currentValue => currentValue - 1); } reset() { this.count.set(0); } }

Bu örnekte, doubleCount ve parityStatus adında iki yeni computed sinyal tanımladık. Bu sinyaller, count sinyalinin değerine bağımlıdır. count sinyali increment(), decrement() veya reset() metotları ile her değiştiğinde, doubleCount ve parityStatus sinyalleri otomatik olarak yeniden hesaplanacak ve şablondaki ilgili yerler güncellenecektir. Konsol çıktılarına dikkat ederseniz, computed bloklarının sadece bağımlı sinyal (count) değiştiğinde çalıştığını göreceksiniz. Bu, Angular Signals'ın sunduğu en önemli performans avantajlarından biridir: gereksiz hesaplamaları önlemek.

Bu basit sayaç örneği, Angular Signals'ın temelini ve reaktif programlamayı ne kadar kolaylaştırdığını net bir şekilde gösteriyor. Bir sonraki bölümde, bu kavramları daha karmaşık bir "Yapılacaklar Listesi" uygulaması üzerinde nasıl kullanabileceğimizi keşfedeceğiz.

Gerçek Dünya Senaryosu: Bir ToDo Uygulamasında Signals Nasıl Kullanılır?

Şimdiye kadar Signals'ın temelini ve basit bir sayaç üzerinde nasıl çalıştığını gördük. Şimdi bu bilgiyi daha gerçekçi ve yaygın bir senaryo olan bir "Yapılacaklar Listesi" (Todo App) uygulamasında kullanalım. Bu, kullanıcı girişini, liste yönetimini ve durum filtrelemeyi Signals ile nasıl ele alabileceğimizi gösterecek.

Kullanıcı Girişini ve Filtrelemeyi signal() ile Yönetmek

Bir Todo uygulaması genellikle bir liste öğesi ekleme, öğeleri tamamlama/işaretleme ve listeyi filtreleme gibi işlevlere sahiptir. Tüm bu uygulama durumlarını (state) Signals kullanarak yöneteceğiz. İşte adım adım bir TodoListComponent örneği:


    // todo-list.component.ts
    import { Component, signal, computed } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { FormsModule } from '@angular/forms'; // [(ngModel)] kullanmak için

    // Todo öğesinin tipini tanımlıyoruz
    interface Todo {
      id: number;
      text: string;
      completed: boolean;
    }

    @Component({
      selector: 'app-todo-list',
      template: 

Yapılacaklar Listesi (Angular Signals ile)

  • {{ todo.text }}
  • Henüz görev yok veya filtrenize uygun sonuç bulunamadı.
, styles: [:host { display: block; max-width: 600px; margin: 20px auto; padding: 20px; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } h3 { color: #333; text-align: center; margin-bottom: 20px; } .input-section, .filter-section { display: flex; gap: 10px; margin-bottom: 15px; } input[type="text"] { flex-grow: 1; padding: 10px; border: 1px solid #ccc; border-radius: 4px; } button { padding: 10px 15px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; transition: background-color 0.2s; } button:hover { background-color: #0056b3; } ul { list-style: none; padding: 0; } li { display: flex; align-items: center; padding: 10px 0; border-bottom: 1px solid #eee; } li:last-child { border-bottom: none; } input[type="checkbox"] { margin-right: 10px; transform: scale(1.2); } span { flex-grow: 1; font-size: 1.1em; } .completed { text-decoration: line-through; color: #888; } .delete-button { background-color: #dc3545; margin-left: auto; padding: 5px 10px; } .delete-button:hover { background-color: #c82333; } .no-todos { text-align: center; color: #666; font-style: italic; padding: 20px; }], standalone: true, imports: [CommonModule, FormsModule] }) export class TodoListComponent { // Uygulamanın durumunu (state) tutan sinyaller todos = signal([]); // Tüm yapılacaklar listesi newTodoText = signal(''); // Yeni eklenecek görevin metni filterText = signal(''); // Filtreleme için kullanılacak metin // filteredTodos bir computed sinyalidir. // todos veya filterText sinyallerinden herhangi biri değiştiğinde yeniden hesaplanır. filteredTodos = computed(() => { const currentFilter = this.filterText().toLowerCase(); return this.todos().filter(todo => todo.text.toLowerCase().includes(currentFilter) ); }); // Yeni bir görev ekleme metodu addTodo() { const text = this.newTodoText().trim(); if (text) { this.todos.update(currentTodos => [ ...currentTodos, { id: Date.now(), text: text, completed: false } ]); this.newTodoText.set(''); // Input'u temizle } } // Görevin tamamlanma durumunu değiştirme metodu toggleCompletion(id: number) { this.todos.update(currentTodos => currentTodos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo ) ); } // Görevi silme metodu deleteTodo(id: number) { this.todos.update(currentTodos => currentTodos.filter(todo => todo.id !== id) ); } // Filtre metnini temizleme metodu clearFilter() { this.filterText.set(''); } }

Bu örnekte, todos, newTodoText ve filterText gibi uygulama durumlarını yönetmek için signal() kullanıyoruz. filteredTodos ise, todos ve filterText sinyallerine bağımlı bir computed() sinyalidir. filterText her değiştiğinde veya yeni bir todo eklendiğinde/silindiğinde (todos değiştiğinde), filteredTodos otomatik olarak yeniden hesaplanır ve UI güncellenir. Bu yaklaşım, Angular'ın değişim algılama mekanizması için iş yükünü büyük ölçüde azaltır, çünkü sadece ilgili sinyallerin değiştiği yerler yeniden render edilir. Kullanıcı deneyimi daha akıcı hale gelir ve kod daha kolay anlaşılır.

effect() ile Yan Etkileri Yönetme: LocalStorage'a Kaydetme

Bir Todo uygulamasında sıkça karşılaşılan bir ihtiyaç da, görev listesini tarayıcının yerel depolama alanına (localStorage) kaydetmek ve uygulamayı her açtığınızda geri yüklemektir. Bu, bir yan etki örneğidir ve effect() kullanarak bunu kolayca yapabiliriz.


    // todo-list-with-effect.component.ts
    import { Component, signal, computed, effect } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { FormsModule } from '@angular/forms';

    interface Todo {
      id: number;
      text: string;
      completed: boolean;
    }

    @Component({
      selector: 'app-todo-list-with-effect',
      template: 

Yapılacaklar Listesi (Effect ile LocalStorage)

  • {{ todo.text }}
  • Henüz görev yok veya filtrenize uygun sonuç bulunamadı.
, styles: [:host { display: block; max-width: 600px; margin: 20px auto; padding: 20px; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } h3 { color: #333; text-align: center; margin-bottom: 20px; } .input-section, .filter-section { display: flex; gap: 10px; margin-bottom: 15px; } input[type="text"] { flex-grow: 1; padding: 10px; border: 1px solid #ccc; border-radius: 4px; } button { padding: 10px 15px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; transition: background-color 0.2s; } button:hover { background-color: #0056b3; } ul { list-style: none; padding: 0; } li { display: flex; align-items: center; padding: 10px 0; border-bottom: 1px solid #eee; } li:last-child { border-bottom: none; } input[type="checkbox"] { margin-right: 10px; transform: scale(1.2); } span { flex-grow: 1; font-size: 1.1em; } .completed { text-decoration: line-through; color: #888; } .delete-button { background-color: #dc3545; margin-left: auto; padding: 5px 10px; } .delete-button:hover { background-color: #c82333; } .no-todos { text-align: center; color: #666; font-style: italic; padding: 20px; }], standalone: true, imports: [CommonModule, FormsModule] }) export class TodoListWithEffectComponent { todos = signal([]); newTodoText = signal(''); filterText = signal(''); filteredTodos = computed(() => { const currentFilter = this.filterText().toLowerCase(); return this.todos().filter(todo => todo.text.toLowerCase().includes(currentFilter) ); }); constructor() { // Uygulama yüklendiğinde localStorage'dan görevleri geri yükle const storedTodos = localStorage.getItem('angular-signals-todos'); if (storedTodos) { this.todos.set(JSON.parse(storedTodos)); } // effect kullanarak todos sinyali her değiştiğinde localStorage'a kaydet effect(() => { console.log('Todos listesi değişti, localStorage güncelleniyor.'); localStorage.setItem('angular-signals-todos', JSON.stringify(this.todos())); }, { allowSignalWrites: true }); // effect içinde sinyal yazma izni (nadiren kullanılır, dikkat!) } addTodo() { const text = this.newTodoText().trim(); if (text) { this.todos.update(currentTodos => [ ...currentTodos, { id: Date.now(), text: text, completed: false } ]); this.newTodoText.set(''); } } toggleCompletion(id: number) { this.todos.update(currentTodos => currentTodos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo ) ); } deleteTodo(id: number) { this.todos.update(currentTodos => currentTodos.filter(todo => todo.id !== id) ); } clearFilter() { this.filterText.set(''); } }

Bu güncellenmiş örnekte, constructor içinde bir effect tanımladık. Bu effect, todos sinyalinin değerine bağımlıdır. todos sinyali her değiştiğinde (yeni görev eklendiğinde, tamamlanma durumu değiştirildiğinde veya bir görev silindiğinde), effect bloğu otomatik olarak tetiklenir ve güncel todo listesini localStorage'a kaydeder. Uygulama ilk yüklendiğinde de, constructor içinde localStorage'dan verileri okuyarak todos sinyalini başlatıyoruz.

Bu iki gerçek dünya senaryosu, Signals'ın reaktif durumu yönetme, türetilmiş değerler oluşturma ve yan etkileri ele alma konusunda ne kadar güçlü ve esnek olduğunu göstermektedir. Geliştiriciler, bu yapı taşlarını kullanarak daha az hata riski taşıyan, daha performanslı ve daha okunabilir kodlar yazabilirler.

Angular Signals ile İleri Seviye Kullanım Senaryoları ve İpuçları

Angular Signals, sadece basit reaktivite sağlamakla kalmaz, aynı zamanda daha karmaşık senaryolarda da geliştiricilere güçlü araçlar sunar. Deneyimli Angular geliştiricileri için Signals'ın RxJS ile entegrasyonu, performans optimizasyonu ve mobil uyumluluk bağlamındaki rolü, konunun derinliğini gösterir.

RxJS ve Signals Arasındaki Köprüler: toSignal() ve toObservable()

Angular ekosistemi uzun süredir RxJS ile yoğun bir şekilde iç içe geçmiştir. Mevcut projelerde RxJS kodunu Signals'a dönüştürmek veya tam tersi, kritik bir ihtiyaçtır. Angular, bu geçişi kolaylaştırmak için iki yardımcı fonksiyon sunar:

  • toSignal(observable): Bir RxJS Observable'ı bir Signal'a dönüştürür. Observable yeni bir değer yaydığında, Signal'ın değeri güncellenir. Bu, mevcut RxJS tabanlı veri akışlarınızı Signals ekosistemine sorunsuz bir şekilde entegre etmenizi sağlar.

    
            import { Component, signal } from '@angular/core';
            import { toSignal } from '@angular/core/rxjs-interop'; // Yeni import
            import { interval, map } from 'rxjs';
    
            @Component({
              selector: 'app-rxjs-to-signal',
              template: 

    RxJS'ten Signal'a Dönüşüm

    Sayaç (RxJS ile başlatıldı): {{ rxjsCounter() }}

    , standalone: true }) export class RxjsToSignalComponent { // Her 1 saniyede bir artan bir RxJS Observable'ı private counter$ = interval(1000).pipe(map(val => val + 1)); // Observable'ı bir Signal'a dönüştürüyoruz rxjsCounter = toSignal(this.counter$, { initialValue: 0 }); constructor() { // Signal değeri değiştiğinde konsola yazdırabiliriz // effect(() => { // console.log('RxJS sayacı sinyal değeri:', this.rxjsCounter()); // }); } }


    Bu sayede, mevcut bir servisinizden gelen Observable tabanlı veriyi, bileşenlerinizde daha basit bir şekilde Signal olarak tüketebilirsiniz.

  • toObservable(signal): Bir Signal'ı bir RxJS Observable'a dönüştürür. Signal'ın değeri değiştiğinde, Observable yeni bir değer yayar. Bu, Signals'tan gelen verileri karmaşık RxJS operatörleri (örneğin, debounceTime, switchMap) ile işlemek istediğinizde kullanışlıdır.

    
            import { Component, signal } from '@angular/core';
            import { toObservable } from '@angular/core/rxjs-interop'; // Yeni import
            import { debounceTime, distinctUntilChanged } from 'rxjs';
    
            @Component({
              selector: 'app-signal-to-rxjs',
              template: 

    Signal'dan RxJS'e Dönüşüm

    Son Aranan Terim (debounce ile): {{ debouncedSearchTerm }}

    , standalone: true }) export class SignalToRxjsComponent { searchTerm = signal(''); debouncedSearchTerm = ''; constructor() { toObservable(this.searchTerm).pipe( debounceTime(500), // 500ms gecikme distinctUntilChanged() // Sadece değer değiştiyse devam et ).subscribe(term => { this.debouncedSearchTerm = term; console.log('Debounce edilmiş arama terimi:', term); // Burada arama API'ı çağrısı yapılabilir }); } }


    Bu ikili, Angular projelerinde hem RxJS'in gücünden yararlanmaya devam ederken hem de Signals'ın getirdiği basitlik ve performans avantajlarını kullanmanın önünü açar. Hangi yaklaşımın ne zaman kullanılacağı, spesifik kullanım senaryosuna ve takımın tercihine bağlıdır. Genellikle, basit değerler ve türetilmiş durumlar için Signals, karmaşık asenkron veri akışları ve operatör zincirlemeleri için ise RxJS tercih edilir.

Performans Optimizasyonunda Signals'ın Rolü

Signals'ın en büyük faydalarından biri, uygulamanın genel performansına yaptığı katkıdır. Nasıl mı?

  • Daha İnce Taneli Değişim Algılama: Signals, Zone.js'in tüm bileşen ağacını kontrol etmesinin aksine, yalnızca değişen sinyale bağımlı olan spesifik DOM parçalarını veya bileşenleri günceller. Bu, çok daha az iş yükü ve dolayısıyla daha hızlı güncellemeler anlamına gelir. Özellikle derin bileşen ağaçlarına sahip büyük uygulamalarda bu fark hissedilir olur.
  • Zone.js'siz Uygulamalar: Angular, gelecekte tamamen Zone.js'siz uygulamaların yazılmasına olanak tanımak için çalışıyor. Signals, bu vizyonun temelini oluşturur. Zone.js olmadan, uygulamanın boyutu küçülür, başlangıç süresi hızlanır ve daha da önemlisi, değişim algılama üzerinde tam kontrol sağlanır. Bu, özellikle performansa duyarlı mobil web uygulamaları için kritik bir avantajdır.
  • OnPush Stratejisi ile Sinerji: Signals, OnPush değişim algılama stratejisiyle mükemmel bir uyum içindedir. OnPush kullanan bileşenler, yalnızca input'ları değiştiğinde (veya bir Observable'dan gelen veri yaydığında) yeniden kontrol edilir. Signals ile, bir bileşenin içindeki bir sinyalin değişmesi, o bileşenin OnPush stratejisine rağmen doğru bir şekilde güncellenmesini sağlar. Bu, geliştiricilerin OnPush'un avantajlarından daha kolay yararlanmasına olanak tanır.
  • defer Blokları: Angular 17 ile gelen @defer blokları, şablonun belirli kısımlarını koşullu veya asenkron olarak yüklemenizi sağlar. Bu, sayfa yükleme süresini kısaltarak performansınızı önemli ölçüde artırabilir. Signals, @defer bloklarının ne zaman tetikleneceğini belirlemek için de kullanılabilir, örneğin bir sinyal true olduğunda bir bileşeni yüklemek gibi. Bu, özellikle mobil cihazlarda bant genişliği ve işlem gücü kısıtlı olduğunda büyük fayda sağlar.

    
            
            
            ?defer (when userProfileLoaded()) {
              
            }
          

Mobil uyumlu HTML üretme konusunda, Signals doğrudan CSS veya HTML yapısını etkilemese de, uygulamanın genel performansını artırarak mobil deneyimi dolaylı yoldan iyileştirir. Daha hızlı yüklenen, daha akıcı ve daha az kaynak tüketen bir uygulama, küçük ekranlarda ve sınırlı internet bağlantılarında bile daha iyi performans gösterir. Ayrıca, Signals tabanlı reaktivite, karmaşık ve dinamik kullanıcı arayüzlerini (UI) mobil cihazlarda bile sorunsuz bir şekilde oluşturmanıza olanak tanır. Responsive tasarım ilkeleri elbette HTML ve CSS ile sağlanır, ancak Signals'ın performansa katkısı, bu tasarımların mobil platformlarda da sorunsuz çalışmasını destekler.

Örneğin, mobil cihazlarda farklı düzenler ve bileşen boyutları için genellikle CSS media query'leri kullanılır. Signals, bu düzenler arasındaki geçişlerin neden olduğu yeniden render işlemlerini minimize ederek mobil cihazlardaki gecikmeyi azaltır. Aşağıda, mobil uyumlu bir tasarım için tipik bir media query örneği verilmiştir (bu bir CSS örneği olsa da, mantığını anlamak için faydalıdır):


    /* Temel stil: Küçük ekranlar (mobil) için */
    .container {
      flex-direction: column;
      padding: 10px;
    }
    .card {
      width: 100%;
      margin-bottom: 15px;
    }

    /* Orta boy ekranlar (tablet) için kurallar */
    @media (min-width: 768px) {
      .container {
        flex-direction: row;
        flex-wrap: wrap;
        justify-content: space-around;
        padding: 20px;
      }
      .card {
        width: 48%; /* İki sütun */
        margin-bottom: 20px;
      }
    }

    /* Büyük ekranlar (masaüstü) için kurallar */
    @media (min-width: 1200px) {
      .card {
        width: 30%; /* Üç sütun */
      }
    }
  

Bu tür media query'ler ile değişen bir UI'da, Signals, her layout değişikliğinde gereksiz tüm bileşenlerin yeniden kontrol edilmesini engelleyerek sadece değişen veya bağlı olan kısımları güncel tutar, böylece özellikle mobil cihazlarda akıcılık ve enerji verimliliği sağlar.

Angular Signals Kullanmanın Avantajları ve Potansiyel Zorlukları Nelerdir?

Her yeni teknoloji gibi, Angular Signals da geliştiricilere önemli avantajlar sunarken, beraberinde bazı potansiyel zorlukları da getirmektedir. Bu bölüm, Signals'ın genel resmini daha iyi anlamanıza yardımcı olacak.

Avantajlar:

  • Basitlik ve Okunabilirlik: Signals, reaktif durumu yönetmeyi çok daha basit hale getirir. RxJS'in karmaşık operatör zincirlemelerine kıyasla, Signals'ın signal(), computed() ve effect() yapıları daha sezgiseldir ve öğrenme eğrisi daha düşüktür. Şablonlarda değerlere doğrudan fonksiyon çağrısı gibi erişmek, kodu daha okunabilir kılar.
  • Daha İyi Performans: En önemli avantajlarından biridir. Signals, çok daha ince taneli bir reaktivite modeli sunar. Yalnızca değişen sinyale bağımlı olan bileşenler veya DOM parçaları güncellenir. Bu, gereksiz yeniden render işlemlerini ortadan kaldırır ve özellikle büyük, karmaşık uygulamalarda performansı önemli ölçüde artırır.
  • Öngörülebilir Reaktivite: Signals, veri akışını daha öngörülebilir hale getirir. Bir sinyalin değeri değiştiğinde, hangi computed veya effect bloklarının tetikleneceği açıktır. Bu, hata ayıklamayı kolaylaştırır ve beklenmedik davranışları azaltır.
  • Geliştirici Deneyimi: Daha az kazan doğurucu kod (boilerplate code), daha az ngOnChanges veya AsyncPipe kullanımı, geliştiricilerin daha hızlı ve daha keyifli bir şekilde kod yazmasını sağlar. Reaktif durum yönetimini basitleştirmek, genel geliştirici verimliliğini artırır.
  • Zone.js'e Bağımlılığın Azalması: Signals, Angular'ın Zone.js'e olan bağımlılığını azaltma yolunda atılmış büyük bir adımdır. Gelecekte Zone.js'siz uygulamalar, daha küçük paket boyutları ve daha hızlı başlangıç süreleri vaat ediyor.

Potansiyel Zorluklar:

  • Öğrenme Eğrisi: RxJS'e alışkın olan geliştiriciler için Signals'ın farklı bir reaktif model sunması, başlangıçta bir öğrenme eğrisi yaratabilir. signal() değerlerine erişmek için () kullanmak gibi sentaktik farklılıklar alışkanlık gerektirebilir.
  • Mevcut Projelerde Geçiş Stratejileri: Büyük ve hali hazırda var olan Angular projelerinde Signals'a tam geçiş yapmak, belirli bir planlama ve kademeli bir süreç gerektirebilir. Neyse ki toSignal() ve toObservable() gibi yardımcı fonksiyonlar bu geçişi kolaylaştırır.
  • effect Kullanımı Sırasında Dikkat Edilmesi Gerekenler: effect'ler güçlüdür ancak dikkatli kullanılmalıdır. Yanlış kullanıldığında sonsuz döngülere yol açabilir veya gereksiz yan etkiler tetikleyebilirler. effect'lerin sadece yan etkileri yönetmek için kullanılması ve içinde sinyal değerini tekrar değiştirmekten kaçınılması önemlidir (aksi halde allowSignalWrites: true seçeneği ile açıkça belirtilmelidir, ki bu nadir bir durum olmalıdır).
  • RxJS ile Birlikte Kullanım Karmaşası: Hem RxJS hem de Signals'ı aynı projede kullanmak, özellikle yeni başlayanlar için ne zaman hangisinin kullanılacağına dair bir kafa karışıklığı yaratabilir. Net sınırlar ve en iyi uygulamalar belirlemek önemlidir.

Özetle, Angular Signals, modern Angular geliştirme için güçlü ve umut vadeden bir ektir. Doğru kullanıldığında, uygulamalarınızın performansını ve kod kalitesini önemli ölçüde iyileştirme potansiyeline sahiptir. Ancak her araç gibi, ne zaman ve nasıl kullanılacağını anlamak için zaman ayırmak gerekmektedir.

Sıkça Sorulan Sorular (SSS) ve Yanıtları

Sinyaller RxJS'in yerini alacak mı?
Hayır, Sinyaller RxJS'in yerini almayacak, aksine onunla birlikte var olacak. Sinyaller basit, senkronize reaktif değerler ve türetilmiş durumlar için mükemmeldir. RxJS ise karmaşık asenkron veri akışlarını, zamanlamayı ve gelişmiş operatör zincirlemelerini yönetmek için hala en güçlü araçtır. Angular ekibi, ikisinin birlikte nasıl en iyi şekilde çalışacağını kolaylaştırmak için toSignal() ve toObservable() gibi yardımcı fonksiyonlar sağlamıştır.
Sinyallerle Zone.js'e ihtiyacımız kalacak mı?
Angular ekibinin uzun vadeli hedefi, tamamen Zone.js'siz Angular uygulamaları mümkün kılmaktır ve Sinyaller bu hedefe ulaşmada önemli bir adımdır. Şu an için Zone.js hala çoğu Angular uygulamasında kullanılmaktadır. Ancak, Sinyaller sayesinde Zone.js'in kapsamı azaltılabilir ve gelecekte Zone.js olmadan da Angular uygulamaları çalıştırılabilir hale gelecektir. Bu da daha iyi performans ve daha küçük uygulama boyutları anlamına gelir.
Neden effect() kullanırken dikkatli olmalıyız?
effect() fonksiyonları yan etkileri yönetmek için tasarlanmıştır (örneğin konsola log yazma, localStorage'a kaydetme, DOM manipülasyonu). Ancak, bir effect içinde bir sinyali değiştirmek, dikkatli olunmazsa sonsuz döngülere yol açabilir. Bu nedenle, effect'lerin amacı sadece yan etkileri gerçekleştirmek olmalı, yeni bir reaktif durum yaratmak veya bir sinyali güncellemek için kullanılmamalıdır (istisnai durumlar dışında). Genellikle, UI'daki değerleri doğrudan şablonda göstermek veya türetilmiş değerler için computed() kullanmak daha güvenli ve öngörülebilir bir yaklaşımdır.
Sinyaller ile state yönetimi nasıl yapılır?
Sinyaller, uygulamanın durumunu (state) yönetmek için oldukça etkilidir. Ana bileşende veya bir servis içinde signal() ile uygulama durumunu tutabilir, computed() ile bu durumdan türetilmiş yeni değerler yaratabilir ve effect() ile yan etkileri (örn. API çağrıları, localStorage'a kaydetme) yönetebilirsiniz. Daha karmaşık senaryolarda, Signals tabanlı bir state yönetim servisi oluşturabilir veya mevcut NgRx gibi state yönetim kütüphanelerini Signals ile birleştirebilirsiniz. Sinyaller, hem yerel bileşen durumları hem de global uygulama durumu için basit ve performanslı bir çözüm sunar.
Sinyaller hangi Angular sürümlerinde kullanılabilir?
Angular Signals, Angular'ın 16. sürümüyle geliştirici önizlemesi (developer preview) olarak tanıtıldı ve 17. sürüm itibarıyla kararlı bir özellik olarak genel kullanıma sunuldu. Dolayısıyla, Angular 17 ve sonraki sürümlerini kullanarak projelerinizde Signals'ı güvenle kullanabilirsiniz. Önceki Angular sürümleri için bu özellik mevcut değildir.

Angular Signals, reaktif programlamaya modern, verimli ve sezgisel bir yaklaşım getirerek Angular geliştirme deneyimini yeniden şekillendiriyor. Bu rehberde, temel kavramlardan gerçek dünya uygulamalarına, RxJS ile entegrasyondan performans optimizasyonuna kadar Signals'ın birçok yönünü ele aldık. Gördüğünüz gibi, Signals ile daha az kodla daha fazla iş yapabilir, uygulamalarınızın performansını artırabilir ve daha yönetilebilir bir kod tabanı oluşturabilirsiniz.

Angular ekibinin geleceğe yönelik vizyonunda Signals, Zone.js'siz, daha modüler ve daha optimize edilmiş uygulamaların temelini oluşturuyor. Bu yeni paradigmayı benimsemek, hem kariyerinizde hem de projelerinizde size önemli avantajlar sağlayacaktır. Artık Signals'ı kendi Angular uygulamalarınızda denemek ve sunduğu faydaları bizzat deneyimlemek için gerekli bilgiye sahipsiniz. Hadi kodlamaya başlayın!

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.