javascript - OnPush ve şablonlar: Değişiklikleri üst bileşene iletme


İki şablona sahip bir üst bileşenim var (Stackblitz):

  1. ilk olan basit bir veri bağlı metin içerir
  2. ikinci, veri modelini güncelleyen bir çocuk bileşen içerir (bu demo’da constructor ve ngOnInit’den)

İşte üst bileşen şablonu:

<ng-template [ngIf]="true">
  <div>
    <b>Value from TemplateComponent: </b>{{ dataService.value | async }}
  </div>
</ng-template>

<ng-template [ngIf]="true">
    <template-component></template-component>
</ng-template>

Hem ebeveyn hem de çocuk bileşenler ChangeDetectionStrategy.OnPush ve bu bir gerekliliktir.

Sorun şu ki, veri güncellemeleri ngOnInit'den (ve sonrasından) itibaren çocuk bileşende başlıyor ve bunu ebeveyn bileşende değişiklik algılama yakalamıyor. Bu, demodaki ebeveyn bileşenin şunu göstermesine neden oluyor:

TemplateComponent'ten değer: constructor'dan değer

yerine

TemplateComponent'tan değer: ngOnInit'ten değer

çünkü CD yalnızca yapıcı aşamasında bir güncelleme alır, ancak ngOnInit ve sonrasında almaz:

export class TemplateComponent implements OnInit {
    public constructor(public readonly dataService: DataService) {
      this.dataService.setValue("value from constructor");
    }

    public ngOnInit(): void {
      // This update is NOT picked up by CD in parent component
      this.dataService.setValue("value from ngOnInit");
    }
}

Bu markForCheck() yaklaşımının dışında, çocuk bileşende basit bir @Output olayı da denedim, ancak bu açıkça çalışmıyor, çünkü çocuk ng-template aracılığıyla oluşturuluyor ve aslında ebeveyn bileşenin doğrudan çocuğu değil.

ng-template ile oluşturulmuş kontrolden veriyi ebeveyne aktarmak için en temiz ve Angular'ın OnPush dostu yaklaşım hakkında bir fikir verebilir mi biri?

Not: Tarafından

en temiz ve Angular'ın OnPush-dostu yaklaşımı

, Yani tüm parçaları (bileşenler ve servis) gevşek bağlı tutan bir şeyden bahsediyorum, örneğin setTimeout() veya diğer kirli çözümleri dahil etmeden.

Cevap #1

Observable'ları, async pipe'ları ve OnPush stratejisini kullanma fikrini kavradınız, ancak sorun şu ki biraz daha derine inip Angular bileşen yaşam döngüsü olaylarının nasıl çalıştığını anlamanız gerekiyor.

Ne olacağını görelim (basitleştirilmiş versiyon)

  1. Üst bileşen constructor()
  2. Alt bileşen constructor()
  3. Üst bileşen onInit() - @Input şablonlarını ve ngIf yönergelerini değerlendir
  4. Alt bileşen onInit() - ebeveyn tarafından kullanılan bazı özelliklerde değişiklikleri tetikler
  5. Ebeveyn afterViewInit()

Çocuk bileşende onInit bir kez çağrıldığında, ebeveyn bileşendeki direktifler zaten değerlendirilmişti! Angular perspektifinden [ngIf] şablonu içindeki içerik zaten olması gereken haldedir ve değişiklik algılamayı tekrar çalıştırmaya veya herhangi bir şeyi güncellemeye gerek yoktur.

Çocuk bileşende onInit() sırasında üst bileşende kullanılan verileri değiştirmemelisiniz, aksi takdirde kesinlikle bir şeyleri bozarsınız.

Tüm bileşenler tamamen oluşturulup değişiklik tetiklenene kadar beklerseniz, tüm görünümler güncellenecektir.

ör. çocuğunuzun bileşeninde:

setTimeout(()=>this.dataService.setValue("hello"),1000) 

Bu da işe yarar, eğer üst bileşeninizde ngAfterViewInit içinde markForCheck yapmayı denerseniz

  ngAfterViewInit(): void {
    this.changeDetector.markForCheck();
  }

veya *ngIf kullanmazsanız, değer yeniden değerlendirilecektir.

<div><b>Value from regular div: </b>{{ dataService.value | async }}</div>

Bahsedilen tüm geçici çözümler tavsiye edilmez, sadece alt bileşenin onInit yönteminden veriyi değiştirme ve her şey yolunda olur.

İlgili makaleler