NgFor, Angular2'de Boru ile verileri güncellemez


114

Bu senaryoda, aşağıdakilerin bulunduğu görünüme bir öğrenci listesi (dizi) görüntülüyorum ngFor:

<li *ngFor="#student of students">{{student.name}}</li>

Listeye başka bir öğrenci eklediğimde güncellenmesi harika.

Ancak, bunu bir verdiğimde pipeiçin filteröğrenci adıyla,

<li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>

Filtreleme öğrenci adı alanına bir şey yazana kadar listeyi güncellemez.

İşte plnkr bağlantısı .

Hello_world.html

<h1>Students:</h1>
<label for="newStudentName"></label>
<input type="text" name="newStudentName" placeholder="newStudentName" #newStudentElem>
<button (click)="addNewStudent(newStudentElem.value)">Add New Student</button>
<br>
<input type="text" placeholder="Search" #queryElem (keyup)="0">
<ul>
    <li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>
</ul>

sort_by_name_pipe.ts

import {Pipe} from 'angular2/core';

@Pipe({
    name: 'sortByName'
})
export class SortByNamePipe {

    transform(value, [queryString]) {
        // console.log(value, queryString);
        return value.filter((student) => new RegExp(queryString).test(student.name))
        // return value;
    }
}


14
Ekle pure:falsesizin Boru içinde ve changeDetection: ChangeDetectionStrategy.OnPushsizin Bileşen'de.
Eric Martinez

2
Teşekkürler @EricMartinez. İşe yarıyor. Ama biraz açıklayabilir misin?
Chu Son

2
Ayrıca, .test()filtre işlevinizde KULLANMAMANIZI öneririm . Kullanıcı bir gibi özel anlam karakterleri içeren dize girmesi halinde Onun çünkü: *ya +vb kod kıracak. .includes()Özel işlevle sorgu dizesi kullanmanız veya kaçmanız gerektiğini düşünüyorum .
Eggy

6
Borunuzu eklemek pure:falseve durum bilgisine sahip hale getirmek sorunu çözecektir. ChangeDetectionStrategy'yi değiştirmek gerekli değildir.
pixelbits

2
Bunu okuyan herkes için, Açılı Borular için dokümantasyon çok daha iyi hale geldi ve burada tartışılan pek çok şeyin üzerinden geçiyor. Bunu kontrol et.
0xcaff

Yanıtlar:


154

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}}, powervefactor girişlerdir.

Bu soru için, "#student of students | sortByName:queryElem.value", studentsve queryElem.valuegirişleri ve boru sortByNamebilgisi olmayan / saftır. studentsbir 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.valuedeğ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ü studentsreferans 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 onPushdeğ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 checkedbu ayarda bu hatayı almıyoruz . Bunun nedeni, bir onPushbileş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: onPushdeğ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 onPushmodundaki bileşenlerle kullanılabilir .


Tüm bunlardan sonra, @ Eric'in ve @ pixelbits'in yanıtlarının bir kombinasyonunu sevdiğimi düşünüyorum: onPushbileş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.


Sorunla ilgili ayrıntılı açıklamanız için teşekkür ederiz. Dizilerin değişiklik tespitinin eşitlik karşılaştırması yerine kimlik olarak uygulanması garip olsa da. Bunu Observable ile çözmek mümkün olabilir mi?
Martin Nowak

@MartinNowak, dizi bir Gözlemlenebilir mi diye soruyorsanız ve boru durumsuzsa ... Bilmiyorum, denemedim.
Mark Rajcok

Başka bir çözüm, çıktı dizisinin olay dinleyicileriyle girdi dizisindeki değişiklikleri izlediği saf bir kanal olabilir.
Tuupertunut

Senin Plunker'ını çatalladım ve benim için onsuz çalışıyor onPush değişiklik tespiti plnkr.co/edit/gRl0Pt9oBO6038kCXZPk?p=preview
Dan

28

As Eric Martinez ekleyerek Açıklamalarda belirttiği pure: falsesizin için Pipedekoratör ve changeDetection: ChangeDetectionStrategy.OnPushsizin için Componentsorununuzu çözecektir dekoratör. İşte çalışan bir plunkr. Olarak değiştirmek ChangeDetectionStrategy.Alwaysde işe yarar. İşte nedeni.

Borularla ilgili angular2 kılavuzuna göre :

Borular varsayılan olarak durumsuzdur. Dekoratörün pureözelliğini olarak ayarlayarak boruyu durum bilgili olacak şekilde ilan etmeliyiz . Bu ayar, Angular'ın değişiklik algılama sistemine, girişi değişmiş olsun ya da olmasın bu borunun çıkışını her döngüde kontrol etmesini söyler.@Pipefalse

Gelince ChangeDetectionStrategy , varsayılan olarak, tüm bağlamaları her döngüsü kontrol edilir. Bir pure: falsekanal eklendiğinde, performans nedenlerinden dolayı değişiklik algılama yönteminin 'den' CheckAlwayse değiştiğine inanıyorum CheckOnce. İle OnPush, Bileşen için bağlamalar yalnızca bir girdi özelliği değiştiğinde veya bir olay tetiklendiğinde kontrol edilir. Önemli bir parçası olan değişiklik algılayıcıları hakkında daha fazla bilgi angular2için aşağıdaki bağlantılara göz atın:


"Bir pure: falseboru eklendiğinde, değişiklik algılama yönteminin CheckAlways'den CheckOnce'a değiştiğine inanıyorum" - bu, belgelerden alıntı yaptığınız ile uyuşmuyor. Anladığım kadarıyla şu: durumsuz bir kanalın girdileri her döngüde varsayılan olarak kontrol edilir. Bir değişiklik varsa, borunun transform()yöntemi çıktıyı (yeniden) üretmek için çağrılır. Durum bilgisi olan bir borunun transform()yöntemi her döngü olarak adlandırılır ve çıktısı değişiklikler için kontrol edilir. Ayrıca stackoverflow.com/a/34477111/215945 adresine de bakın .
Mark Rajcok

@MarkRajcok, Oops, haklısınız, ancak durum buysa, neden değişiklik tespit stratejisini değiştirmek işe yarar?
0xcaff

1
Harika soru. Görünüşe göre değişiklik algılama stratejisi değiştirildiğinde OnPush(yani, bileşen "değişmez" olarak işaretlendiğinde), durum bilgisi olan boru çıktısının "stabilize" olması gerekmiyor - yani, transform()yöntem yalnızca bir kez çalıştırılıyor gibi görünüyor (muhtemelen tümü bileşen için değişiklik tespiti yalnızca bir kez çalıştırılır). Bunu @ pixelbits'in cevabıyla karşılaştırın, burada transform()yöntem birden çok kez çağrılır ve stabilize edilmesi gerekir (pixelbitlerin diğer cevabına göre ), dolayısıyla aynı dizi referansını kullanma ihtiyacı.
Mark Rajcok

@MarkRajcok Söylediğiniz şey verimlilik açısından doğruysa, bu muhtemelen daha iyi bir yoldur, çünkü bu özel kullanım durumunda transformyalnızca yeni veriler çıktı stabilize olana kadar birden çok kez değil, itildiğinde çağrılır, değil mi?
0xcaff

1
Muhtemelen, uzun soluklu cevabıma bakın. Keşke Angular 2'de değişiklik tespiti hakkında daha fazla belge olsaydı.
Mark Rajcok

22

Demo Plunkr

ChangeDetectionStrategy'yi değiştirmenize gerek yoktur. Durum bilgisi olan bir Boru uygulamak, her şeyin çalışmasını sağlamak için yeterlidir.

Bu durum bilgisi olan bir kanaldır (başka değişiklik yapılmadı):

@Pipe({
  name: 'sortByName',
  pure: false
})
export class SortByNamePipe {
  tmp = [];
  transform (value, [queryString]) {
    this.tmp.length = 0;
    // console.log(value, queryString);
    var arr = value.filter((student)=>new RegExp(queryString).test(student.name));
    for (var i =0; i < arr.length; ++i) {
        this.tmp.push(arr[i]);
     }

    return this.tmp;
  }
}

ChangeDectection öğesini onPush olarak değiştirmeden bu hatayı aldım: [ plnkr.co/edit/j1VUdgJxYr6yx8WgzCId?p=preview](Plnkr) Expression 'students | sortByName:queryElem.value in HelloWorld@7:6' has changed after it was checked. Previous value: '[object Object],[object Object],[object Object]'. Current value: '[object Object],[object Object],[object Object]' in [students | sortByName:queryElem.value in HelloWorld@7:6]
Chu Son

1
Değerleri neden SortByNamePipe'da yerel bir değişkene depolamanız ve ardından onu dönüştürme işlevi @pixelbits'e döndürmeniz gerektiğini örnekleyebilir misiniz? Ayrıca, changeDetion.OnPush ile 4 kez ve onsuz 4 kez dönüştürme işlevi aracılığıyla Açısal döngü olduğunu fark ettim
Chu Son

@ChuSon, muhtemelen önceki bir sürümün günlüklerine bakıyorsunuz. Bir değerler dizisi döndürmek yerine, bir değer dizisine bir referans döndürüyoruz , ki bu değişiklik algılanabilir, inanıyorum. Pixelbits, cevabınız daha mantıklı.
0xcaff

Yukarıdaki yorumlarda bahsedilen ChuSon hatası ve yerel bir değişken kullanma ihtiyacı ile ilgili olarak diğer okuyucuların yararı için başka bir soru oluşturuldu ve piksel bitleri ile yanıtlandı.
Mark Rajcok

19

Gönderen açısal dokümantasyon

Saf ve saf olmayan borular

İki boru kategorisi vardır: saf ve saf olmayan. Borular varsayılan olarak saftır. Şimdiye kadar gördüğün her pipo saftı. Saf bayrağını yanlış olarak ayarlayarak bir boruyu saflaştırırsınız. FlyingHeroesPipe'ı şu şekilde saflaştırabilirsiniz:

@Pipe({ name: 'flyingHeroesImpure', pure: false })

Bunu yapmadan önce, saf bir pipo ile başlayarak saf ve saf olmayan arasındaki farkı anlayın.

Saf borular Angular, yalnızca giriş değerinde saf bir değişiklik algıladığında saf bir boruyu yürütür. Tam bir değişiklik, ilkel bir girdi değerinde (Dize, Sayı, Boolean, Sembol) veya değiştirilmiş bir nesne başvurusundaki (Tarih, Dizi, İşlev, Nesne) bir değişikliktir.

Açısal, (bileşik) nesneler içindeki değişiklikleri yok sayar. Bir girdi ayını değiştirirseniz, bir girdi dizisine eklerseniz veya bir girdi nesnesi özelliğini güncellerseniz saf bir kanal çağırmaz.

Bu kısıtlayıcı görünebilir ama aynı zamanda hızlıdır. Bir nesne referans kontrolü hızlıdır - farklılıklar için derin bir kontrolden çok daha hızlıdır - böylece Angular hem boru yürütmeyi hem de bir görünüm güncellemesini atlayıp atlayamayacağını hızlıca belirleyebilir.

Bu nedenle, değişim tespit stratejisi ile yaşayabildiğiniz zaman saf bir boru tercih edilir. Yapamadığın zaman saf olmayan boruyu kullanabilirsin.


Cevap doğru, ancak yayınlarken biraz daha fazla çaba göstermek güzel olurdu
Stefan

2

Saf yapmak yerine: yanlış. Bu bileşendeki değeri derin bir şekilde kopyalayabilir ve değiştirebilirsiniz this.students = Object.assign ([], NEW_ARRAY); NEW_ARRAY, değiştirilmiş dizidir.

Açısal 6 için çalışır ve diğer açısal sürümler için de çalışmalıdır.


0

Bir geçici çözüm: Yapıcıda Boruyu manuel olarak içe aktarın ve bu boruyu kullanarak dönüştürme yöntemini çağırın

constructor(
private searchFilter : TableFilterPipe) { }

onChange() {
   this.data = this.searchFilter.transform(this.sourceData, this.searchText)}

Aslında bir pipoya bile ihtiyacın yok


0

Ekstra boru parametresine ekleyin ve dizi değişikliğinden hemen sonra değiştirin ve saf boru ile bile liste yenilenecektir

let öğe öğeleri | Boru: param


0

Bu kullanım durumunda, veri filtreleme için ts dosyasında Pipe'ımı kullandım. Performans için saf borular kullanmaktan çok daha iyidir. Bunun gibi ts kullanın:

import { YourPipeComponentName } from 'YourPipeComponentPath';

class YourService {

  constructor(private pipe: YourPipeComponentName) {}

  YourFunction(value) {
    this.pipe.transform(value, 'pipeFilter');
  }
}

0

saf olmayan borular oluşturmak performans açısından maliyetlidir. dolayısıyla saf olmayan kanallar oluşturmayın, bunun yerine verilerde değişiklik olduğunda verilerin kopyasını oluşturarak ve orijinal veri değişkeninde kopyanın referansını yeniden atayarak veri değişkeninin referansını değiştirin.

            emp=[];
            empid:number;
            name:string;
            city:string;
            salary:number;
            gender:string;
            dob:string;
            experience:number;

            add(){
              const temp=[...this.emps];
              const e={empid:this.empid,name:this.name,gender:this.gender,city:this.city,salary:this.salary,dob:this.dob,experience:this.experience};
              temp.push(e); 
              this.emps =temp;
              //this.reset();
            } 
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.