Açısal'da bir @Input () değeri değiştiğinde nasıl algılanır?


471

Bir üst bileşen ( CategoryComponent ), bir alt bileşen ( videoListComponent ) ve bir ApiService var.

Ben bu çalışma para cezası çoğu yani her bileşen json api erişebilir ve gözlemlenebilir üzerinden ilgili verileri alabilirsiniz.

Şu anda video listesi bileşeni sadece tüm videoları alıyor, bunu sadece belirli bir kategorideki videolara filtrelemek istiyorum, bunu categoryId'i çocuğa geçirerek başardım @Input().

CategoryComponent.html

<video-list *ngIf="category" [categoryId]="category.id"></video-list>

Bu çalışır ve üst CategoryComponent kategorisi değiştikten sonra categoryId değeri üzerinden geçer @Input()ama sonra bu VideoListComponent bu tespit ve APIService (yeni categoryId ile) ile video dizisi yeniden istemek gerekir.

AngularJS de ben $watchdeğişken üzerinde bir yapardı . Bununla başa çıkmanın en iyi yolu nedir?




dizi değişiklikleri için: stackoverflow.com/questions/42962394/…
dasfdsa

Yanıtlar:


721

Aslında, bir girdi açısal2 + 'da alt bileşende değiştiğinde algılama ve harekete geçmenin iki yolu vardır:

  1. NgOnChanges () yaşam döngüsü yöntemini eski yanıtlarda da belirtildiği gibi kullanabilirsiniz :
    @Input() categoryId: string;

    ngOnChanges(changes: SimpleChanges) {

        this.doSomething(changes.categoryId.currentValue);
        // You can also use categoryId.previousValue and 
        // categoryId.firstChange for comparing old and new values

    }

Dokümantasyon Bağlantıları: ngOnChanges, SimpleChanges, SimpleChange
Demosu Örnek: Bu pistona bakın

  1. Alternatif olarak, aşağıdaki gibi bir girdi özelliği ayarlayıcı da kullanabilirsiniz :
    private _categoryId: string;

    @Input() set categoryId(value: string) {

       this._categoryId = value;
       this.doSomething(this._categoryId);

    }

    get categoryId(): string {

        return this._categoryId;

    }

Dokümantasyon Bağlantısı: Buraya bakın .

Demo Örneği: Bu dalma makinesine bakın .

HANGİ YAKLAŞIMI KULLANMALISINIZ?

Bileşeninizde birden fazla giriş varsa, ngOnChanges () kullanırsanız, ngOnChanges () içinde bir defada tüm girişler için tüm değişiklikleri alırsınız. Bu yaklaşımı kullanarak, değişen girişin mevcut ve önceki değerlerini karşılaştırabilir ve buna göre eylem yapabilirsiniz.

Ancak, yalnızca belirli bir giriş değiştiğinde (ve diğer girişleri umursamadığınızda) bir şey yapmak istiyorsanız, bir input özelliği ayarlayıcı kullanmak daha kolay olabilir. Ancak bu yaklaşım, değiştirilen girişin önceki ve geçerli değerlerini karşılaştırmak için yerleşik bir yöntem sağlamaz (ngOnChanges yaşam döngüsü yöntemiyle kolayca yapabilirsiniz).

EDIT 2017-07-25: AÇILI DEĞİŞİM ALGILAMA BAZI KOŞULLARIN ALTINDA YANAMAYABİLİR

Normalde, üst bileşen alt öğeye geçirdiği verileri her değiştirdiğinde, verilerin bir JS ilkel veri türü (dize, sayı, boole) olması koşuluyla, hem ayarlayıcı hem de ngOnChanges için değişiklik algılaması tetiklenir . Ancak, aşağıdaki senaryolarda, tetiklenmeyecektir ve çalışması için fazladan işlem yapmanız gerekir.

  1. Üst öğeden Alt öğeye veri aktarmak için iç içe geçmiş bir nesne veya dizi (JS ilkel veri türü yerine) kullanıyorsanız, kullanıcı tarafından yanıt olarak da belirtildiği gibi değişiklik algılama (ayarlayıcı veya ngchanges kullanarak) tetiklenmeyebilir: muetzerich. Çözümler için buraya bakın .

  2. Verileri açısal bağlamın dışında (yani harici olarak) değiştiriyorsanız, açısal değişiklikleri bilmeyecektir. Harici değişikliklerin açısal farkındalığını sağlamak ve böylece değişiklik algılamayı tetiklemek için bileşeninizde ChangeDetectorRef veya NgZone'u kullanmanız gerekebilir. Bakın bu .


8
Girişler ile ayarlayıcıların kullanılması konusunda çok iyi bir nokta, daha kapsamlı olduğu için bunu kabul edilen cevap olarak güncelleyeceğim. Teşekkürler.
Jon Catmull

2
@trichetriche, ebeveyn girişi değiştirdiğinde ayarlayıcı (set yöntemi) çağrılır. Aynı şey ngOnChanges () için de geçerlidir.
Alan CS

Görünüşe göre değil ... Bunu anlamaya çalışacağım, muhtemelen yanlış yaptığım bir şey.

1
Bu soruyu tökezledim, ayarlayıcıyı kullanarak değerleri karşılaştırmaya izin vermeyeceğinizi söylediniz, ancak bu doğru değil, doSomethingyönteminizi çağırabilir ve yeni değeri ayarlamadan önce yeni ve eski değerleri 2 bağımsız değişkene alabilirsiniz. yöntemi ayarlamak ve sonra çağırmak için eski değeri saklamak olacaktır.
T.Aoukar

1
@ T.Aoukar Diyorum ki giriş ayarlayıcı, ngOnChanges () gibi eski / yeni değerleri karşılaştırmayı yerel olarak desteklemiyor. Eski değeri (bahsettiğiniz gibi) yeni değerle karşılaştırmak için her zaman kesmek kullanabilirsiniz.
Alan CS

105

ngOnChanges()Bileşeninizde yaşam döngüsü yöntemini kullanın .

ngOnChanges, veriye bağlı özellikler kontrol edildikten hemen sonra ve en az birinin değişip değişmediği görüntüleme ve içerik alt öğeleri kontrol edilmeden hemen önce çağrılır.

İşte Dokümanlar .


6
Bu çalışmalar bir değişken yeni değeri, örn olarak ayarlandığında MyComponent.myArray = [], ancak bir giriş değeri değişmiş örn ise MyComponent.myArray.push(newValue), ngOnChanges()tetiklenmez. Bu değişikliği nasıl yakalayacağınız hakkında bir fikriniz var mı?
Levi Fuller

4
ngOnChanges()yuvalanmış bir nesne değiştiğinde çağrılmaz. Belki burada
muetzerich

33

SimpleChangesİşlev imzasında türü kullanırken konsolda, derleyicide ve IDE'de hatalar alıyordum . Hataları önlemek için any, imzadaki anahtar kelimeyi kullanın.

ngOnChanges(changes: any) {
    console.log(changes.myInput.currentValue);
}

DÜZENLE:

Jon'un işaret ettiği gibi, SimpleChangesnokta gösteriminden çok köşeli gösterim kullanıldığında imzayı kullanabilirsiniz .

ngOnChanges(changes: SimpleChanges) {
    console.log(changes['myInput'].currentValue);
}

1
SimpleChangesDosyanızın üst kısmındaki arayüzü içe aktardınız mı ?
Jon Catmull

@Jon - Evet. Sorun imzanın kendisi değil, çalışma zamanında SimpleChanges nesnesine atanan parametrelere erişmekti. Örneğin, bileşenimde Kullanıcı sınıfımı girdiye (yani <my-user-details [user]="selectedUser"></my-user-details>) bağladım . Bu özelliğe, çağrılarak SimpleChanges sınıfından erişilir changes.user.currentValue. Bildirim userçalışma zamanında bağlıdır ve SimpleChanges nesnesinin bir parçası değildir
Darcy

1
Bunu denedin mi? ngOnChanges(changes: {[propName: string]: SimpleChange}) { console.log('onChanges - myProp = ' + changes['myProp'].currentValue); }
Jon Catmull

@Jon - Köşeli ayraç gösterimine geçmek hile yapar. Cevabımı alternatif olarak ekleyecek şekilde güncelledim.
Darcy

1
'@ açısal / çekirdek'ten {SimpleChanges} içe aktarın; Yerel bir daktilo türünde değil, açısal dokümanlardaysa, büyük olasılıkla 'açısal / çekirdek' olan açısal bir modülden gelir
BentOnCoding

13

En güvenli bahis paylaşılan ile gitmektir yerine hizmet a @Inputparametresi. Ayrıca, @Inputparametre karmaşık iç içe nesne türündeki değişiklikleri algılamaz.

Basit bir örnek hizmet aşağıdaki gibidir:

Service.ts

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class SyncService {

    private thread_id = new Subject<number>();
    thread_id$ = this.thread_id.asObservable();

    set_thread_id(thread_id: number) {
        this.thread_id.next(thread_id);
    }

}

Component.ts

export class ConsumerComponent implements OnInit {

  constructor(
    public sync: SyncService
  ) {
     this.sync.thread_id$.subscribe(thread_id => {
          **Process Value Updates Here**
      }
    }

  selectChat(thread_id: number) {  <--- How to update values
    this.sync.set_thread_id(thread_id);
  }
}

Benzer bir uygulamayı diğer bileşenlerde de kullanabilirsiniz; tüm bileşenleriniz aynı paylaşılan değerleri paylaşacaktır.


1
Teşekkürler Sanket. Bu, yapmak için çok iyi bir nokta ve uygun olduğunda bunu yapmanın daha iyi bir yolu. @Input değişikliklerini algılama ile ilgili özel sorularıma yanıt verdiği için kabul edilen yanıtı bırakacağım.
Jon Catmull

1
@Input parametresi, karmaşık bir iç içe nesne türü içindeki değişiklikleri algılamaz , ancak nesnenin kendisi değişirse, tetiklenir , değil mi? Örneğin "üst" girdi parametresine sahibim, bu nedenle üst öğeyi bir bütün olarak değiştirirsem değişiklik algılaması etkinleşir, ancak parent.name öğesini değiştirirsem olmaz mı?
yogibimbi

Çözümünüzün daha güvenli olması için bir neden belirtmelisiniz
Joshua Kemmerer

1
@JoshuaKemmerer @Inputparametresi, karmaşık iç içe nesne türündeki değişiklikleri algılamaz, ancak basit yerel nesnelerde yapar.
Sanket Berde

6

Sadece adında başka Ömrü kanca olduğunu eklemek istiyorum DoCheckeğer yararlıdır @Inputdeğer ilkel bir değer değil.

Bir şekilde bir Array sahip Inputpatlamaz bu yüzden OnChangesiçerik değişiklikleri (Açısal yaptığı denetimi 'basit' ve derin olduğu için bu kadar Dizi Dizisi içerik değişmiş olsa dahi, yine de bir Array) zaman olayı.

Daha sonra görünümümü değiştirilmiş Dizi ile güncellemek isteyip istemediğime karar vermek için bazı özel kontrol kodları uygularım.


6
  @Input()
  public set categoryId(categoryId: number) {
      console.log(categoryId)
  }

lütfen bu yöntemi kullanmayı deneyin. Bu yardımcı olur umarım


4

Ayrıca, üst bileşendeki (CategoryComponent) değişiklikleri tetikleyen ve alt bileşendeki abonelikte ne yapmak istediğinizi gözlemleyebilirsiniz. (videoListComponent)

service.ts 
public categoryChange$ : ReplaySubject<any> = new ReplaySubject(1);

-----------------
CategoryComponent.ts
public onCategoryChange(): void {
  service.categoryChange$.next();
}

-----------------
videoListComponent.ts
public ngOnInit(): void {
  service.categoryChange$.subscribe(() => {
   // do your logic
});
}

2

Burada ngOnChanges, input özelliğiniz değiştiğinde daima tetiklenir:

ngOnChanges(changes: SimpleChanges): void {
 console.log(changes.categoryId.currentValue)
}

1

Bu çözüm bir proxy sınıfı kullanır ve aşağıdaki avantajları sunar:

  • Tüketicinin RXJS'nin gücünden faydalanmasını sağlar
  • Şimdiye kadar önerilen diğer çözümlerden daha kompakt
  • Daha türgüvenli kullanmaktan dahangOnChanges()

Örnek kullanım:

@Input()
public num: number;
numChanges$ = observeProperty(this as MyComponent, 'num');

Fayda fonksiyonu:

export function observeProperty<T, K extends keyof T>(target: T, key: K) {
  const subject = new BehaviorSubject<T[K]>(target[key]);
  Object.defineProperty(target, key, {
    get(): T[K] { return subject.getValue(); },
    set(newValue: T[K]): void {
      if (newValue !== subject.getValue()) {
        subject.next(newValue);
      }
    }
  });
  return subject;
}

0

NgOnChange uygulamak og onChange () yöntemini kullanmak istemiyorsanız, valueChanges olayı (VB) ile belirli bir öğenin değişikliklerine de abone olabilirsiniz .

 myForm= new FormGroup({
first: new FormControl()   

});

this.myForm.valueChanges.subscribe(formValue => {
  this.changeDetector.markForCheck();
});

markForCheck () yöntemi bu bildirimde kullanıldığından dolayı yazılmıştır:

  changeDetection: ChangeDetectionStrategy.OnPush

3
Bu sorudan tamamen farklıdır ve yardımcı insanlar kodunuzun farklı bir tür değişiklikle ilgili olduğunu bilmelidir. Soru, bir bileşenin DIŞINDAKİ veri değişimi hakkında ve daha sonra da bileşeninizin verilerin değiştiğini bilmesini ve yanıt vermesini istemiştir. Cevabınız, bileşenin YEREL olan bir formdaki girdiler gibi şeylerde bileşenin içindeki değişiklikleri algılamaktan bahsediyor.
rmcsharry

0

Ayrıca bir EventEmitter öğesini Giriş olarak da iletebilirsiniz. Bu tho en iyi uygulama olup olmadığından emin değilim ...

CategoryComponent.ts:

categoryIdEvent: EventEmitter<string> = new EventEmitter<>();

- OTHER CODE -

setCategoryId(id) {
 this.category.id = id;
 this.categoryIdEvent.emit(this.category.id);
}

CategoryComponent.html:

<video-list *ngIf="category" [categoryId]="categoryIdEvent"></video-list>

Ve VideoListComponent.ts içinde:

@Input() categoryIdEvent: EventEmitter<string>
....
ngOnInit() {
 this.categoryIdEvent.subscribe(newID => {
  this.categoryId = newID;
 }
}

Çözümünüzü desteklemek için lütfen faydalı bağlantılar ve kod snippet'leri sağlayın.
mishsx

Bir örnek ekledim.
Cédric Schweizer
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.