javascript - OnPush ve şablonlar: Değişiklikleri üst bileşene iletme
İki şablona sahip bir üst bileşenim var (Stackblitz):
- ilk olan basit bir veri bağlı metin içerir
- ikinci, veri modelini güncelleyen bir çocuk bileşen içerir (bu demo’da
constructorvengOnInit’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)
- Üst bileşen
constructor() - Alt bileşen
constructor() - Üst bileşen
onInit()- @Input şablonlarını ve ngIf yönergelerini değerlendir - Alt bileşen
onInit()- ebeveyn tarafından kullanılan bazı özelliklerde değişiklikleri tetikler - 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.
