Sitemap
NG Turkey

Angular hakkında her düzeyden Türkçe makaleler okuyabileceğiniz bir blog sayfası. Angular’ı derinlemesine öğrenmek veya bilgi birikimini aktarmak isteyenlerin buluştuğu bir ortam. Topluluğumuzun Discord grubuna katılıp sorularınızı sorabilirsiniz.

Angular Change Detection Nedir?

5 min readOct 23, 2020

--

Press enter or click to view image in full size

Nedir bu konu? Başlıktan da anlaşılacağı üzere Angular’ın view’larımızı güncellemek için kullandığı change detection mekanizması. Nasıl çalışır? Mantığı nedir? Geliştiricilere hangi noktalarda bu mekanizmaya müdahale etme şansı verilmiştir gibi konuları anlamaya çalışacağız. Hafiften Angular’ın lifecycle’ına da değinmek durumunda kalacağız. Geliştirme aşamasında başımıza sürekli bela olan ExpressionChangedAfterItHasBeenCheckedError hatasını neden alıyoruz, almamak için ne yapmalıyız gibi performans açısından önemli olan konulara değineceğiz. Bu nedenle uzun bir yazı olabilir. Konuya giriş yapmadan önce temel Angular bilgisine sahip olduğunuzu varsayarak ilerleyeceğimi belirteyim.

Change Detection

Nedir?

Basit anlamda söylemek gerekirse component’larımızda yaptığımız değişikliklerin algılanarak view’larımızın (HTML) güncellenmesidir. Angular hemen hemen her işlemde yeni state ve eski state’i karşılaştırarak farklılık olup olmadığına bakarak bunu yapar. Bu şekilde söyleyince sanki çok performanssız birşeymiş gibi duruyor ama değil. Milisaniyeler içirisinde yüzlerce binlerce component’ın karşılaştırmasını yapabilmektedir. Tabii ki gelişmiş kompleks uygulamarda bir performans problemi yarattığı söylenebilir. Ancak geliştiricilere bunun önüne geçilebilmesi için bazı imkanlar tanınmıştır.

Diğer tüm SPA framework’lerinde buna benzer yapılar vardır. Angular’ın en çok karşılaştırıldığı diğer bir popüler ( hatta daha popüler ) framework olan React’de ise bu daha farklı işlenir. Basitçe anlatmak gerekirse react mevcut DOM’un bir sanal (Virtual DOM) versiyonunu yaratak karşılaştırmayı state üzerinden değil DOM üzerinden yapar. Yani her durumda SPA frameworkleri bir comparison yaparak view’larımızı günceller diyebiliriz.

Ne zaman çalışır?

Default olarak (component decorator’ünde değiştirmediyseniz) tüm browser event’larında (click, keyup, mousemove vs.), component inputlarının güncellenmesinde, XHR eventlarında (HttpRequest), timer’larda (setTimeout, setInterval) kısacası her interaction’da Angular change detection’ı çalıştırır ve compare ederek karşılaştığı farklılıkları DOM’da günceller.

Default olarak kastettiğim tam olarak component decorator’ünün changeDetection özelliğidir. Bu özellik iki farklı değer alır:

  • ChangeDetectionStrategy.Default
  • ChangeDetectionStrategy.OnPush

Siz özellikle OnPush olarak belirtmesseniz Default kabul edilir. Her component’a bu özellik ayrı ayrı set edilebilir. OnPush’un detaylarına ayrıca değineceğiz. Biz default durumunda ne yaşanıyor öncelikle bunu kavrayalım.

ChangeDetectionStrategy.Default

Ne demiştik;

Change detection tüm browser event’larında (click, keyup, mousemove vs.), component inputlarının güncellenmesinde, XHR eventlarında (HttpRequest), timer’larda (setTimeout, setInterval) kısacası her interaction’da çalışır.

Aslında bu durum ChangeDetectionStrategy.Default olarak tanımlandığında bu şekilde gerçekleşir. OnPush’ta işler biraz değişiyor.

Peki change detection’ın devreye girmesi ne demek? Devreye girdiğinde ne yapıyor biraz da bu konu üzerinde duralım.

Angular component’larımızı bir tree şeklinde saklar. Root component dışında her component’ın bir parent’ı vardır. Yukarıda bahsettiğimiz durumlar herhangi bir component’ta gerçekleşir ise change detection devreye girer ve tree’de bulunan tüm component’lar için kontrol yapar. Terminolojide bu duruma “dirty checking” denir. Cümle ile açıklayacak olursak; birşeyler oldu ve bazı şeyler güncellenmiş olabilir. İyisi mi herşeyi kontrol edeyim :) şeklinde bir çözüm üretir. Aşağıdaki görselde durum net olarak gösterilmiştir.

Click to source

Durumu daha detaylı anlayabilmek için basit bir örnek hazırlayalım. Aşağıdaki örnekte dört adet child component var DoCheck ile change detection’ın kaç kere çalıştığını ekrana yazdırıyoruz.

DoCheck’e kısaca değinecek olur isek; change detection çalıştıktan sonra (hangi component tarafından tetiklendiği farketmeksizin) tüm component’lar için çalışan Angular’ın lifecyle hook’u diyebiliriz. OnPush modunda sadece en üstteki OnPush olan component için çalışır, child’ları için çalışmaz. Bu da bize kurduğumuz yapıya göre change detection’ı manuel tetikleme imkanı sağlayabilir.

Angular hooklarına daha detaylı bir şekilde farklı bir yazıda değineceğim. Şimdilik DoCheck’in her change detection işleminden sonra çalıştığını bilmeniz yeterli.

Örneğimizde dört child component’ın da default strategy ile çalışığını görüyoruz. Uygulama ilk açıldığınıda her component’da bize change detection’ın iki defa çalıştığını gösteriyor. Peki neden bir değil de iki? Angular development zamanında componentlar’ın AfterViewChecked hook’undan sonra change detection’ı bir defa daha çalıştırır. Bu durum production’da yoktur. Neden bu şekilde yapar detayına daha sonra değineceğiz ancak bu bilgi ExpressionChangedAfterItHasBeenCheckedError hatasının nereden geldiğini az çok belli etmiştir :)

Örneğimize dönecek olur isek;

Child component’ların herhangi birinde mouse move event’ını tetiklediğimizde change detection tüm component’lar için çalışıyor. Görselde açıkladığımız durumu canlı olarak görmüş oluyoruz. Array example component’ında ise input’a yeni bir eleman yazıp ekle button’una tıkladığımızda yine aynı şekilde diğer üç child için change detection tekrar çalışıyor.

Get Emre Hızlı’s stories in your inbox

Join Medium for free to get updates from this writer.

Bu örneği birde timeout ekleyerek gözlemleyelim. Timeout’u sadece child1'e ekleyip sonuca bakalım.

Uygulama başladığında change detection tüm component’lar için çalıştı. Bir (1) saniye sonra child1'den tetiklenen timeout nedeni ile tüm component’lar için tekrar çalıştı. Bu gibi durumlar performans kaygıları yaşamanıza sebebiyet verebilir. Yaptığımız değişikliklerden hangi component’ların etkilendiğini bilmemize rağmen gereksiz yere tüm component’larda detection çalışıyor. Her ne kadar hızlı çalışıyor olsa da bu durumu istemeyebiliriz. Peki ben change detection’ın sadece mouse’u hareket ettirdiğim component için çalışmasını istesem ne yapmam gerekirdi? Bu durumda devreye OnPush stratejisi giriyor.

ChangeDetectionStrategy.OnPush

OnPush modunda dirty checking yapılmaz. Change detection’ın çalıştığı durumlar şunlardır;

  • @Input() referanslarının değişmesi
  • Kendisinin ya da child component’ların herhangi bir event tetiklemesi
  • View template’lerinde async ile kullanılan observable’ların yeni bir value emit etmesi
  • ChangeDetectorRef ile manuel tetiklenmesi

Bu dört durum dışında OnPush olan component’larda change detection çalışmaz.

Click to source

Son örneğimizdeki child componentları OnPush olarak tanımlayarak sonucu tekrar gözlemleyelim.

Uygulama ilk açıldığında tüm componentlarda cdCount 1 olarak görünüyor. Yani bu demek oluyor ki setTimeout() onPush durumunda changeDetection’ın çalışmasını sağlamıyor. Aynı şekilde mousemove event’ı da sadece tetiklendiği component için change detection’ı çalıştırıyor. Diğer component’larda herhangi bir dom güncellenmesi olmuyor.

Diğer taraftan array example parent’ının DoCheck hook’unun çalıştığını ancak child’ın da çalışmadığını tespit edebiliyoruz. Aynı zamanda listeye yeni meyveler eklediğimizde dom’da güncelleme yapılmadığını farkediyoruz. Sonuç olarak herşey beklendiği gibi çalışıyor.

Şimdi ilk önce array example’da view’ın güncellenmeme sorunun halledelim. Ne demiştik OnPush durumunda @Input()’ların referansları değiştiğinde change detection çalışır. Biz array’a yeni elemanı push metodunu kullanarak eklediğimiz için referansta bir değişiklik olmuyor. Bu nedenle change detection bunu algılamıyor. İlk olarak referansın değişmesini zorlayarak bu problemi çözebiliriz. Önce bu örneğe bakalım:

Artık listeye yeni bir eleman eklendiğinde listenin referansı yenilendiği için view’ın güncellendiğini görüyoruz. Bazı durumlarda bu mümkün olmayabilir. Yapı gereği referansı değiştirmek istemeyebilirsiniz. Peki bu durumda OnPush strategy’sini kullanarak sorunu nasıl çözeceğiz? Hemen bunu inceleyelim.

throw new NotImplementedException();

Child component’da changeDetectorRef’i kullanarak change detection’ın çalışmasını parent tarafından update metodu ile zorladık. Bu yöntemde child’ın view’ını güncellemesi için parent tarafından müdahale etmek zorunda kaldık. Peki bu sorumluluğu child’ın kendisine vermek isteseydik nasıl bir yapı kurmamız gerekirdi?

Array’a item ekleme sorumluluğunu child’e verdik ve ekledikten sonra markForCheck’i çağırdık. Az çok detectChanges ve markForCheck arasındaki farkı anlamış olabilirsiniz. Yine de daha net bir şekilde açıklamak gerekirse şunları söyleyebiliriz:

detectChanges() vs. markForCheck()

Aralarındaki en büyük fark detectChanges change detection’ı anında tetiklerken markForCheck tetiklemez. Peki hangi soruna çözüm getirirler?

detectChanges change detection’ı component ve onun tüm child’ları (descendant’lar dahil) için anında çalıştırır. Bazı durumlarda child’larda change detection’ın çalışmasına gerek yoktur. İşte bu durumda markForCheck devreye giriyor. markForCheck ilgili componentın ancestor’larını işaretler ve bir sonraki change detection cycle’ında bu işaretli componentlarda change detection’a dahil olurlar. markForCheck sadece onPush modunda anlamlıdır. Default modda herhangi bir işlevi yoktur. Bu iki metodun kullanımında yapılan en sık hata genelde gereksiz yere detectChanges’in kullanılması olur. markForCheck performans açısından daha iyi olduğu için OnPush modunda genelde işlerinizi markForCheck ile çözmeye çalışmakta fayda vardır.

ChangeDetection konusu bu kadarla sınırlı değil tabii ki. ngOnChanges ve ngDoCheck’in kullanımı, NgZone ile kodunuzun angular içinde veya dışında çalışması, changeDetectorRef’in detach(), reattach() ve checkNoChanges() yöntemleri gibi değinmedimiz konular var. Ancak bu yazı içinde girecek olursak konu baya uzayacak. Bu konulara farklı bir yazıda daha detaylı olarak değinmeyi tercih ederim.

Vakit ayırdığınız için teşekkürler. Umarım faydalı olabilmişimdir. Bu konu hakkında ya da Angular’ın diğer konuları hakkında sorularınız olur ise mail atmaktan çekinmeyin :)

info@emrehizli.com

--

--

NG Turkey
NG Turkey

Published in NG Turkey

Angular hakkında her düzeyden Türkçe makaleler okuyabileceğiniz bir blog sayfası. Angular’ı derinlemesine öğrenmek veya bilgi birikimini aktarmak isteyenlerin buluştuğu bir ortam. Topluluğumuzun Discord grubuna katılıp sorularınızı sorabilirsiniz.