วิธีการตรวจสอบเมื่อมีการเปลี่ยนแปลงค่า @Input () ในเชิงมุม?


470

ฉันมีองค์ประกอบหลัก ( CategoryComponent ), องค์ประกอบย่อย ( videoListComponent ) และ ApiService

ฉันมีการทำงานที่ดีที่สุดนี้คือแต่ละส่วนสามารถเข้าถึง json api และรับข้อมูลที่เกี่ยวข้องผ่านทาง observables

ปัจจุบันองค์ประกอบรายการวิดีโอเพิ่งได้รับวิดีโอทั้งหมดที่ฉันต้องการที่จะกรองนี้เพื่อวิดีโอเพียงในหมวดหมู่เฉพาะอย่างยิ่งผมประสบความสำเร็จนี้โดยผ่าน CategoryID @Input()กับเด็กผ่านทาง

CategoryComponent.html

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

สิ่งนี้ทำงานได้และเมื่อมีการเปลี่ยนแปลงหมวดหมู่@Input()parentComponent หลักค่า categoryId จะถูกส่งผ่านผ่านแต่ฉันต้องตรวจพบสิ่งนี้ใน VideoListComponent และขออาร์เรย์วิดีโออีกครั้งผ่าน APIService (พร้อมกับ categoryId ใหม่)

ใน AngularJS ฉันจะทำ a $watchบนตัวแปร วิธีที่ดีที่สุดในการจัดการกับสิ่งนี้คืออะไร?



ดูการเปลี่ยนแปลงการป้อนข้อมูล: - angulartutorial.net/2017/12/watch-input-changes-angular-4.html
Prashobh

สำหรับการเปลี่ยนแปลงอาร์เรย์: stackoverflow.com/questions/42962394/…
dasfdsa

คำตอบ:


720

อันที่จริงมีสองวิธีในการตรวจจับและดำเนินการเมื่ออินพุตเปลี่ยนแปลงในองค์ประกอบลูกในเชิงมุม 2 +:

  1. คุณสามารถใช้วิธีวงจรชีวิต ngOnChanges ()ดังที่กล่าวไว้ในคำตอบที่เก่ากว่า:
    @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

    }

เอกสารลิงค์: ngOnChanges, SimpleChanges, SimpleChange
ตัวอย่างสาธิต: ดูที่plunker นี้

  1. อีกทางหนึ่งคุณยังสามารถใช้setter คุณสมบัติอินพุตดังนี้:
    private _categoryId: string;

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

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

    }

    get categoryId(): string {

        return this._categoryId;

    }

การเชื่อมโยงเอกสารอ้างอิง: ดูที่นี่

ตัวอย่างการสาธิต: ดูที่พลั่วเกอร์นี้

คุณควรใช้วิธีการใด

หากองค์ประกอบของคุณมีหลายอินพุตดังนั้นหากคุณใช้ ngOnChanges () คุณจะได้รับการเปลี่ยนแปลงทั้งหมดสำหรับอินพุตทั้งหมดพร้อมกันภายใน ngOnChanges () การใช้วิธีการนี้คุณสามารถเปรียบเทียบค่าปัจจุบันและค่าก่อนหน้าของอินพุตที่เปลี่ยนแปลงและดำเนินการตามความเหมาะสม

อย่างไรก็ตามหากคุณต้องการทำอะไรบางอย่างเมื่อมีการเปลี่ยนแปลงอินพุตเพียงครั้งเดียว (และคุณไม่สนใจอินพุตอื่น) มันอาจง่ายกว่าที่จะใช้ setter คุณสมบัติอินพุต อย่างไรก็ตามวิธีนี้ไม่ได้มีวิธีการเปรียบเทียบค่าก่อนหน้าและปัจจุบันของอินพุตที่มีการเปลี่ยนแปลง (ซึ่งคุณสามารถทำได้อย่างง่ายดายด้วยเมธอดวงจรชีวิต ngOnChanges)

แก้ไข 2017-07-25: การเปลี่ยนแปลงการตรวจจับอาจจะไม่เกิดไฟไหม้ภายใต้วงจรบางอย่าง

โดยปกติแล้วการตรวจสอบการเปลี่ยนแปลงสำหรับทั้งหมาและ ngOnChanges จะยิงเมื่อใดก็ตามที่องค์ประกอบของผู้ปกครองที่มีการเปลี่ยนแปลงข้อมูลที่ส่งผ่านไปยังลูกได้หากข้อมูลที่เป็นประเภทข้อมูลดั้งเดิม JS (สตริงจำนวนบูล) อย่างไรก็ตามในสถานการณ์ต่อไปนี้มันจะไม่เริ่มทำงานและคุณต้องดำเนินการพิเศษเพื่อให้สามารถใช้งานได้

  1. หากคุณใช้วัตถุหรืออาร์เรย์ที่ซ้อนกัน (แทนที่จะเป็นชนิดข้อมูลดั้งเดิมของ JS) เพื่อส่งผ่านข้อมูลจากพาเรนต์ไปยังลูกการตรวจจับการเปลี่ยนแปลง (โดยใช้ setter หรือ ngchanges) อาจไม่ทำงานตามที่กล่าวไว้ในคำตอบโดยผู้ใช้: muetzerich สำหรับการแก้ปัญหาดูที่นี่

  2. หากคุณกำลังกลายพันธุ์ข้อมูลภายนอกบริบทเชิงมุม (เช่นจากภายนอก) ดังนั้นมุมจะไม่ทราบการเปลี่ยนแปลง คุณอาจต้องใช้ ChangeDetectorRef หรือ NgZone ในองค์ประกอบของคุณเพื่อทำให้มุมรับรู้ถึงการเปลี่ยนแปลงภายนอกและทำให้เกิดการตรวจจับการเปลี่ยนแปลง อ้างถึงนี้


8
ประเด็นที่ดีมากเกี่ยวกับการใช้ตัวตั้งค่าที่มีอินพุตฉันจะอัปเดตเป็นคำตอบที่ยอมรับเนื่องจากครอบคลุมมากขึ้น ขอบคุณ
Jon Catmull

2
@trichetriche setter (set method) จะถูกเรียกเมื่อใดก็ตามที่ผู้ปกครองเปลี่ยนอินพุต เช่นเดียวกันกับ ngOnChanges () เช่นกัน
Alan CS

เห็นได้ชัดว่าไม่ใช่ ... ฉันจะพยายามคิดออกมันอาจเป็นสิ่งผิดปกติที่ฉันทำ

1
ฉันเพิ่งสะดุดคำถามนี้สังเกตว่าคุณบอกว่าการใช้ setter ไม่อนุญาตให้ทำการเปรียบเทียบค่า แต่นั่นไม่เป็นความจริงคุณสามารถเรียกdoSomethingวิธีการของคุณและรับอาร์กิวเมนต์ 2 ค่าใหม่และเก่าก่อนที่จะตั้งค่าใหม่ จะเก็บค่าเก่าก่อนการตั้งค่าและเรียกวิธีหลังจากนั้น
T.Aoukar

1
@ T.Aoukar สิ่งที่ฉันพูดคือ input setter ไม่สนับสนุนการเปรียบเทียบค่าเก่า / ค่าใหม่อย่าง ngOnChanges () คุณสามารถใช้แฮ็กเพื่อจัดเก็บค่าเก่า (ตามที่คุณกล่าวถึง) เพื่อเปรียบเทียบกับค่าใหม่
Alan CS

105

ใช้ngOnChanges()วิธีวงจรชีวิตในองค์ประกอบของคุณ

ngOnChanges ถูกเรียกใช้ทันทีหลังจากตรวจสอบคุณสมบัติของ data-bound และก่อนที่จะมีการตรวจสอบมุมมองและเนื้อหาลูกถ้าอย่างน้อยหนึ่งในนั้นมีการเปลี่ยนแปลง

นี่เป็นเอกสาร


6
งานนี้เมื่อตัวแปรถูกกำหนดเป็นเช่นค่าใหม่MyComponent.myArray = []แต่ถ้าค่าการป้อนข้อมูลมีการเปลี่ยนแปลงเช่นMyComponent.myArray.push(newValue), ngOnChanges()ไม่ได้เรียก ความคิดเห็นใด ๆ เกี่ยวกับวิธีบันทึกการเปลี่ยนแปลงนี้
Levi Fuller

4
ngOnChanges()ไม่ถูกเรียกเมื่อวัตถุที่ซ้อนกันเปลี่ยนไป บางทีคุณอาจจะพบทางออกที่นี่
muetzerich

33

ฉันได้รับข้อผิดพลาดในคอนโซลรวมถึงคอมไพเลอร์และ IDE เมื่อใช้SimpleChangesประเภทในลายเซ็นฟังก์ชั่น เพื่อป้องกันข้อผิดพลาดใช้anyคำสำคัญในลายเซ็นแทน

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

แก้ไข:

ดังที่จอนชี้ให้เห็นด้านล่างคุณสามารถใช้SimpleChangesลายเซ็นได้เมื่อใช้เครื่องหมายวงเล็บแทนเครื่องหมายจุด

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

1
คุณนำเข้าSimpleChangesอินเทอร์เฟซที่ด้านบนของไฟล์ของคุณหรือไม่
Jon Catmull

@ จอน - ใช่ ปัญหาไม่ได้เป็นลายเซ็นของตัวเอง แต่การเข้าถึงพารามิเตอร์ที่ได้รับมอบหมายในขณะใช้งานจริงไปยังวัตถุ SimpleChanges ตัวอย่างเช่นในองค์ประกอบของฉันฉันผูกระดับผู้ใช้ของฉันกับการป้อนข้อมูล (เช่น<my-user-details [user]="selectedUser"></my-user-details>) คุณสมบัตินี้ถูกเข้าถึงได้จากระดับ SimpleChanges changes.user.currentValueโดยการเรียก ประกาศuserถูกผูกไว้ที่รันไทม์และไม่ได้เป็นส่วนหนึ่งของวัตถุ SimpleChanges
Darcy

1
คุณเคยลองสิ่งนี้แล้วหรือยัง? ngOnChanges(changes: {[propName: string]: SimpleChange}) { console.log('onChanges - myProp = ' + changes['myProp'].currentValue); }
Jon Catmull

@ จอน - การสลับไปยังเครื่องหมายวงเล็บไม่หลอกลวง ฉันอัปเดตคำตอบเพื่อรวมไว้เป็นทางเลือก
Darcy

1
นำเข้า {SimpleChanges} จาก '@ angular / core'; หากอยู่ในเอกสารเชิงมุมไม่ใช่ประเภทตัวพิมพ์ดั้งเดิมอาจเป็นได้จากโมดูลเชิงมุมน่าจะเป็น 'เชิงมุม / แกน' มากที่สุด
BentOnCoding

13

ทางออกที่ปลอดภัยที่สุดคือการไปกับที่ใช้ร่วมกันให้บริการแทนของ@Inputพารามิเตอร์ นอกจากนี้@Inputพารามิเตอร์จะไม่ตรวจจับการเปลี่ยนแปลงในประเภทวัตถุที่ซ้อนกันที่ซับซ้อน

บริการตัวอย่างง่าย ๆ มีดังนี้:

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);
  }
}

คุณสามารถใช้การนำไปใช้ที่คล้ายกันในส่วนประกอบอื่น ๆ และองค์ประกอบทั้งหมดของคุณจะแชร์ค่าเดียวกัน


1
ขอบคุณ Sanket นี่เป็นจุดที่ดีมากในการทำและเป็นวิธีที่ดีกว่าในการทำ ฉันจะทิ้งคำตอบที่ยอมรับไว้เนื่องจากมันตอบคำถามเฉพาะของฉันเกี่ยวกับการตรวจจับการเปลี่ยนแปลง @Input
Jon Catmull

1
พารามิเตอร์ @Input ตรวจไม่พบการเปลี่ยนแปลงภายในประเภทวัตถุที่ซ้อนกันที่ซับซ้อน แต่ถ้าวัตถุนั้นเปลี่ยนแปลงตัวเองมันจะเริ่มทำงานใช่ไหม เช่นฉันมีพารามิเตอร์อินพุต "parent" ดังนั้นถ้าฉันเปลี่ยน parent โดยรวมการตรวจจับการเปลี่ยนแปลงจะเริ่มทำงาน แต่ถ้าฉันเปลี่ยน parent.name มันจะไม่เกิดอะไรขึ้น
yogibimbi

คุณควรให้เหตุผลว่าทำไมทางออกของคุณจึงปลอดภัย
Joshua Kemmerer

1
@Inputพารามิเตอร์@JoshuaKemmerer ตรวจไม่พบการเปลี่ยนแปลงในประเภทออบเจกต์ซ้อนที่ซับซ้อน แต่ทำในออบเจ็กต์ดั้งเดิมอย่างง่าย
Sanket Berde

6

ฉันต้องการเพิ่มว่ามี hook Lifecycle อีกอันที่เรียกDoCheckว่ามีประโยชน์หาก@Inputค่าไม่ใช่ค่าดั้งเดิม

ฉันมีอาเรย์เป็นแบบInputนี้ดังนั้นจึงไม่ได้ดำเนินการOnChangesเหตุการณ์เมื่อเนื้อหาเปลี่ยนแปลง (เนื่องจากการตรวจสอบว่าแองกูลาร์ทำได้ง่ายและไม่ลึกดังนั้นอาเรย์จึงยังคงเป็นอาเรย์แม้ว่าเนื้อหาในอาเรย์จะเปลี่ยนไป)

จากนั้นฉันก็ใช้รหัสตรวจสอบที่กำหนดเองเพื่อตัดสินใจว่าฉันต้องการอัปเดตมุมมองของฉันด้วยอาร์เรย์ที่เปลี่ยนแปลงหรือไม่


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

โปรดลองใช้วิธีนี้ หวังว่านี่จะช่วยได้


4

นอกจากนี้คุณยังสามารถมีสิ่งที่สังเกตเห็นได้ซึ่งก่อให้เกิดการเปลี่ยนแปลงในองค์ประกอบหลัก (CategoryComponent) และทำสิ่งที่คุณต้องการทำในการสมัครสมาชิกในองค์ประกอบย่อย (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

ที่นี่ ngOnChanges จะเรียกใช้เสมอเมื่อคุณสมบัติการป้อนข้อมูลของคุณเปลี่ยนแปลง:

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

1

โซลูชันนี้ใช้คลาสพร็อกซีและเสนอข้อดีดังต่อไปนี้:

  • อนุญาตให้ผู้บริโภคใช้ประโยชน์จากพลังของRXJS
  • อื่น ๆที่มีขนาดกะทัดรัดกว่าโซลูชั่นอื่น ๆ ที่นำเสนอเพื่อให้ห่างไกล
  • เพิ่มเติมtypesafeกว่าการใช้ngOnChanges()

ตัวอย่างการใช้งาน:

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

ฟังก์ชั่นยูทิลิตี้:

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 ใช้ og onChange () คุณยังสามารถสมัครรับข้อมูลการเปลี่ยนแปลงของรายการที่เฉพาะเจาะจงตามเหตุการณ์ valueChanges , ETC

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

});

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

markForCheck () เขียนเพราะใช้ในการประกาศนี้:

  changeDetection: ChangeDetectionStrategy.OnPush

3
สิ่งนี้แตกต่างจากคำถามอย่างสิ้นเชิงและถึงแม้ว่าผู้คนที่เป็นประโยชน์ควรทราบว่ารหัสของคุณเกี่ยวกับการเปลี่ยนแปลงประเภทอื่น คำถามที่ถามเกี่ยวกับการเปลี่ยนแปลงข้อมูลที่มาจากด้านนอกส่วนประกอบจากนั้นให้ส่วนประกอบของคุณรู้และตอบสนองต่อความจริงที่ว่าข้อมูลมีการเปลี่ยนแปลง คำตอบของคุณกำลังพูดถึงการตรวจจับการเปลี่ยนแปลงภายในส่วนประกอบในสิ่งต่าง ๆ เช่นอินพุตในแบบฟอร์มซึ่งเป็น LOCAL ไปยังส่วนประกอบ
rmcsharry

0

นอกจากนี้คุณยังสามารถส่ง EventEmitter เป็น Input ได้ ไม่แน่ใจว่านี่เป็นวิธีปฏิบัติที่ดีที่สุดหรือไม่ ...

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>

และใน VideoListComponent.ts:

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

โปรดระบุลิงก์ที่เป็นประโยชน์และข้อมูลโค้ดเพื่อสนับสนุนโซลูชันของคุณ
mishsx

ฉันเพิ่มตัวอย่าง
Cédric Schweizer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.