Sorunu ve olası çözümleri tam olarak anlamak için, borular ve bileşenler için Açısal değişiklik algılamayı tartışmamız gerekir.
Boru Değişim Tespiti
Vatansız / saf Borular
Varsayılan olarak, borular durumsuz / saftır. Durumsuz / saf borular, giriş verilerini basitçe çıkış verilerine dönüştürür. Hiçbir şey hatırlamıyorlar, bu yüzden hiçbir özellikleri yok - sadece bir transform()
yöntem. Bu nedenle Angular, durumsuz / saf boruların işlenmesini optimize edebilir: eğer girdileri değişmezse, boruların bir değişiklik algılama döngüsü sırasında yürütülmesine gerek yoktur. Gibi bir boru için {{power | exponentialStrength: factor}}
, power
vefactor
girişlerdir.
Bu soru için, "#student of students | sortByName:queryElem.value"
, students
ve queryElem.value
girişleri ve boru sortByName
bilgisi olmayan / saftır. students
bir dizidir (başvuru).
- Öğrenci eklendiğinde dizi referansı değişmez -
students
değişmez - dolayısıyla durumsuz / saf boru çalıştırılmaz.
- Filtre girişine bir şey yazıldığında
queryElem.value
değişir, dolayısıyla durumsuz / saf boru çalıştırılır.
Dizi sorununu çözmenin bir yolu, öğrenci her eklendiğinde dizi referansını değiştirmektir - yani, her öğrenci eklendiğinde yeni bir dizi oluşturmak. Bunu şununla yapabiliriz concat()
:
this.students = this.students.concat([{name: studentName}]);
Bu işe yarasa da, addNewStudent()
bir boru kullandığımız için yöntemimizin belirli bir şekilde uygulanması gerekmemelidir. Dizimize push()
eklemek için kullanmak istiyoruz .
Durum Bilgili Borular
Durum bilgisi olan kanalların durumu vardır - normalde özellikleri vardır, sadece bir transform()
yöntem değildir . Girdileri değişmemiş olsa bile değerlendirilmeleri gerekebilir. Bir borunun durum bilgili / saf olmadığını belirttiğimizde -pure: false
- o zaman Angular'ın değişiklik algılama sistemi bir bileşeni değişiklikler için kontrol ettiğinde ve bu bileşen durum bilgisi olan bir boru kullandığında, borunun çıkışını, girdisinin değişip değişmediğini kontrol edecektir.
Bu, daha az verimli olmasına rağmen istediğimiz gibi geliyor, çünkü students
referans değişmemiş olsa bile borunun çalışmasını istiyoruz . Boruyu basitçe durum bilgili yaparsak, bir hata alırız:
EXCEPTION: Expression 'students | sortByName:queryElem.value in HelloWorld@7:6'
has changed after it was checked. Previous value: '[object Object],[object Object]'.
Current value: '[object Object],[object Object]' in [students | sortByName:queryElem.value
Göre @ drewmoore cevabı , "Bu hata yalnızca (beta-0 itibariyle varsayılan olarak etkindir) dev modunda olur. Eğer ararsanız enableProdMode()
uygulamayı işe koşulması zaman hata atılan almazsınız." İçin dokümanlarApplicationRef.tick()
devlet:
Geliştirme modunda, tick () ayrıca başka bir değişikliğin algılanmamasını sağlamak için ikinci bir değişiklik algılama döngüsü gerçekleştirir. Bu ikinci döngü sırasında ek değişiklikler alınırsa, uygulamadaki bağlamaların tek bir değişiklik algılama geçişinde çözülemeyen yan etkileri vardır. Bu durumda, Angular bir hata atar, çünkü bir Angular uygulamasında tüm değişiklik tespitinin tamamlanması gereken tek bir değişiklik algılama geçişi olabilir.
Senaryomuzda, hatanın sahte / yanıltıcı olduğuna inanıyorum. Durum bilgisi olan bir borumuz var ve her çağrıldığında çıktı değişebilir - yan etkileri olabilir ve sorun değil. NgFor borudan sonra değerlendirilir, bu nedenle iyi çalışması gerekir.
Ancak, bu hata fırlatılırken gerçekten gelişemeyiz, bu nedenle bir geçici çözüm, boru uygulamasına bir dizi özelliği (yani durum) eklemek ve her zaman bu diziyi döndürmektir. Bu çözüm için @ pixelbits'in cevabına bakın.
Bununla birlikte, daha verimli olabiliriz ve göreceğimiz gibi, boru uygulamasında dizi özelliğine ihtiyacımız olmayacak ve çift değişiklik tespiti için bir geçici çözüme ihtiyacımız olmayacak.
Bileşen Değişikliği Tespiti
Varsayılan olarak, her tarayıcı olayında, Açısal değişiklik algılama, değişip değişmediğini görmek için her bileşenden geçer; girişler ve şablonlar (ve belki başka şeyler?) Kontrol edilir.
Bir bileşenin yalnızca girdi özelliklerine (ve şablon olaylarına) bağlı olduğunu ve girdi özelliklerinin değişmez olduğunu bilirsek, çok daha verimli onPush
değişiklik tespit stratejisini kullanabiliriz. Bu strateji ile, her tarayıcı olayını kontrol etmek yerine, bir bileşen yalnızca girdiler değiştiğinde ve şablon olayları tetiklendiğinde kontrol edilir. Ve görünüşe göre, Expression ... has changed after it was checked
bu ayarda bu hatayı almıyoruz . Bunun nedeni, bir onPush
bileşenin tekrar "işaretlenene" ( ChangeDetectorRef.markForCheck()
) kadar tekrar kontrol edilmemesidir . Dolayısıyla, Şablon bağlamaları ve durum bilgisi olan boru çıktıları yalnızca bir kez yürütülür / değerlendirilir. Durum bilgisi olmayan / saf kanallar, girdileri değişmedikçe yürütülmez. Yani burada hala durum bilgisi olan bir boruya ihtiyacımız var.
@EricMartinez'in önerdiği çözüm budur: onPush
değişiklik algılamalı durum bilgisi olan kanal . Bu çözüm için @ caffinatedmonkey'in cevabına bakın.
Bu çözümle transform()
yöntemin her seferinde aynı diziyi döndürmesine gerek olmadığını unutmayın. Yine de bunu biraz garip buluyorum: durumu olmayan durum bilgisi olan bir boru. Biraz daha düşünürsek ... durum bilgisi olan kanal muhtemelen her zaman aynı diziyi döndürmelidir. Aksi takdirde, yalnızca geliştirme onPush
modundaki bileşenlerle kullanılabilir .
Tüm bunlardan sonra, @ Eric'in ve @ pixelbits'in yanıtlarının bir kombinasyonunu sevdiğimi düşünüyorum: onPush
bileşen izin veriyorsa değişiklik algılama ile aynı dizi referansını döndüren durum bilgisi olan kanal . Durum bilgisi olan kanal aynı dizi referansını döndürdüğünden, kanal yine de yapılandırılmamış bileşenlerle kullanılabilir onPush
.
Plunker
Bu muhtemelen bir Angular 2 deyimi haline gelecektir: eğer bir dizi bir boruyu besliyorsa ve dizi değişebiliyorsa (dizi referansı değil, dizideki öğeler), durum bilgisi olan bir kanal kullanmamız gerekir.
pure:false
sizin Boru içinde vechangeDetection: ChangeDetectionStrategy.OnPush
sizin Bileşen'de.