Angular Signals

Nahit Ferhat Ektaş
4 min readJun 2, 2024

--

Angular ekibinin son dönemde geliştirdiği yenilikler içerisinde şüphesiz en çok ses getireni Signals yapısı oldu.

Signals ile bir değer tanımladığımızda, bu değerde herhangi bir değişiklik olduğunda signal kendini tüketen component’a bunun bilgisini verir.

Peki Angular neden böyle bir özellik getirdi? Yada Signals olmadan önce ne gibi problemler yaşanıyordu? Biraz bunlara bakalım.

@Component({
selector: 'app-root',
standalone: true,
imports: [MatButtonModule],
template: `<div>
<h3>Price: {{ price }}</h3>
<h3>Quantity: {{ quantity }}</h3>
<h3>Total Price: {{ totalPrice }}</h3>
<button mat-flat-button color="primary" (click)="increaseQuantity()">
Increase
</button>
<button mat-flat-button color="warn" (click)="decreaseQuantity()">
Decrease
</button>
</div>`,
styleUrl: './app.component.css',
})
export class AppComponent {
price = 10;
quantity = 3;
totalPrice = this.price * this.quantity;

increaseQuantity() {
this.quantity = this.quantity + 1;
console.log(this.quantity);
}
decreaseQuantity() {
this.quantity = this.quantity - 1;
console.log(this.quantity);
}
}

Yukarıdaki kod blogunda totalPrice değişkeni price ve quantity değişkenine bağımlı ikisinin çarpımı ile total price değeri ekrana yazdırılıyor. Ancak increaseQuantity veya decreaseQuantity fonksiyonu çağırıldığında quantity değeri değişmesine rağmen, buna bağımlı olan Total Price değeri değişmiyor.

Total Price değerini Signals kullanmadan çözmek için, fonksiyonumuza this.totalPrice = this.price * this.quantity; kodunu dahil etmek yeterli olacaktır. Aşağıdaki kod blogunda Total Price değeride artık güncellenecektir.

increaseQuantity() {
this.quantity = this.quantity + 1;
this.totalPrice = this.price * this.quantity;
console.log(this.quantity);
}

Bunun dışında Rxjs kütüphanesini kullanarak da aynı şekilde Total Price değerini güncelleyebiliriz.

Peki Singals yapısına neden ihtiyaç duyuyoruz?

Bu sorunun cevabını verebilmek için, Angular da yer alan Zone js ve Change Detection mekanizmasından biraz bahsetmek gerekiyor.

Zone.js ve Change Detection mekanizmasından bahsettiğim yazıma aşağıdaki linklerden ulaşabilirsiniz.

Kısaca bu yapılardan bahsedecek olursak,

Zone.js uygulama içerisindeki herhangi bir state’in değişme olasılığının olduğu durumları, change detection mekanizmasına haber veren bir kütüphanedir. Change detection mekanizması da, zone.js’in sağladığı bilgi ile tüm component’ları çok kısa süre içerisinde yeniden render eder.

Buradaki sıkıntı Zone.js’in değişikliğin olduğu component’in bilgisini vermiyor olmasından kaynaklanıyor. Hatta bir değişikliğin kesin olup olmadığı bilgisini de vermez. Yalnızca bir işlem gerçekleşti. Örneğin bir rest api’ye istek atıldı, bunun sonucunda bazı state’ler değişmiş olabilir bakış açısı ile tüm component’ler yeniden render edilir.

Angular ilk çıktığı dönemlerdeki web teknolojilerine ve uygulama hacimlerine bakıldığı zaman Zone.js ideal bir çözüm olabilirdi. Ancak bugünkü web teknolojilerinin ilerlemesi ve uygulamaların daha büyük olması performans sorunlarını da beraberinde getirmesine neden olabiliyor.

Change Detection stratejisinin “Default” yerine “OnPush” yapılması ve daha yoğun şekilde Rxjs kütüphanesi kullanılması ile performans iyileştirmeleri yapılsa da problemin kaynağı aslında Angular’ın hangi component’leri yeniden render etmesi gerektiğini bilmiyor olmasıydı.

Bunun yanı sıra yoğun Rxjs kullanımı ve Change Detection stratejisinin OnPush olarak güncellenmesi de Angular kullanımını zorlaştırıyor ve öğrenmenin zor olduğu bir teknoloji haline gelmesine neden oluyordu.

Bu noktada Signals önemli bir avantaj sağlıyor.

Signals, Zone.js’e ihtiyaç duymaz. Signals ile tanımlanan değişkenler, herhangi bir değişiklik sonucunda ilgili signal’a bağımlı olan component’e değişimin bilgisini verir. Böylelikle tüm component’leri yeniden render etmektense yalnızca değişimin etkilendiği component’ler yeniden render edilir.

Yukarıdaki örneğin aynısını Signals kullanarak yapalım.

@Component({
selector: 'app-root',
standalone: true,
imports: [MatButtonModule],
template: `<div>
<h3>Price: {{ price() }}</h3>
<h3>Quantity: {{ quantity() }}</h3>
<h3>Total Price: {{ totalPrice() }}</h3>
<button mat-flat-button color="primary" (click)="increaseQuantity()">
Increase
</button>
<button mat-flat-button color="warn" (click)="decreaseQuantity()">
Decrease
</button>
</div>`,
styleUrl: './app.component.css',
})
export class AppComponent {
price = signal(10);
quantity = signal(3);
totalPrice = computed(() => this.price() * this.quantity());

constructor() {
effect(() => {
console.log(`Price: ${this.price()}`);
console.log(`Quantity: ${this.quantity()}`);
console.log(`Total Price: ${this.totalPrice()}`);
});
}

increaseQuantity() {
this.quantity.set(this.quantity() + 1);
this.price.update((val) => val + 3);
}
decreaseQuantity() {
this.quantity.set(this.quantity() - 1);
}
}

Yukarıdaki örnekte olduğu gibi Signal tanımlamak için,

signal() yazarak parantez içerisine başlangıç değeri vermemiz yeterli olacaktır. Signal oluştururken istediğimiz tipte bir başlangıç değeri verip bir değişkene atamamız gerekmektedir.

Oluşturduğumuz signal’ı kullanmak için ise, atadığımız değişkeni parantez kullanarak çağırmamız gerekiyor.

<h3>Price: {{ price() }}</h3>

Oluşturduğumuz signal’ın değerini değiştirmek için, set veya update fonksiyonundan yararlanabiliriz. Aşağıdaki set() fonksiyonu içerisinde mevcut quantity değerini çağırıyor ve +1 ekliyoruz.

Update() fonksiyonunda ise, value ile tanımladığımız kısım mevcut değer ve yine +1 eklemiş oluyoruz.

this.quantity.set(this.quantity() + 1);
this.price.update((value) => value + 3);

Writable signals ve Computed signals olmak üzere 2 tip signals vardır. Yukarıda yaptığımız Writable singals örneğidir.

Writable Signals, diğer signals değerlerine bağımlı değildir, yalnızca kendi değerlerinin değişimini bildirirler.

price = signal(10);
quantity = signal(3);
totalPrice = this.price() * this.quantity();

this.price() * this.quantity() geriye bir signal değil number tipinde bir değer döndüğü için, price ve quantity değişimi totalPrice’yi etkilemeyecektir.

Eğer price veya quantity’ye bağımlı signals oluşturmak istiyorsak Computed Singals’dan yararlanmamız gerekiyor.

price = signal(10);
quantity = signal(3);
totalPrice = computed(() => this.price() * this.quantity());

Yukarıdaki kod bloğunda olduğu gibi Computed Signal kullandığımızda, Computed Signals bizden bir expression bekler ve bu expression içerisine yazdığımız değerlerin değişimine bağımlı olarak kendini yeniler.

Computed Signals,

1- Readonly olarak çalışır, Writable Signals gibi direk olarak değer ataması yapılmaz.

2- İçerisine tanımlanan değerlerin değişimini takip ederek, değimlere göre kendini update eder.

3- Bağımlı olduğu değer değişmediği sürece mevcut değeri cache de tutar ve kendini tüketen tüm component’lere aynı değeri gönderir.

Signals Effects

Oluşturulan signal değerini ilk okumaya başladığında veya değer her değişime uğradığında bu değere effects ile erişebiliriz.

constructor() {
effect(() => {
console.log(`Price: ${this.price()}`);
console.log(`Quantity: ${this.quantity()}`);
console.log(`Total Price: ${this.totalPrice()}`);
});
}

Oluşturduğumuz signals değerleri değişime uğradığında bu değerler ile ekstra işlemler yapmak istiyorsak(loglama vb.) effects yapısını kullanabiliriz.

Angular ekibi, Signals yapısını her yeni sürümde geliştirmeye devam ediyor. Bu yazıda ile bir başlangıç yapmak istedim. Umarım faydalı olmuştur.

--

--