เพื่อให้เข้าใจปัญหาอย่างถ่องแท้และแนวทางแก้ไขที่เป็นไปได้เราต้องหารือเกี่ยวกับการตรวจจับการเปลี่ยนแปลงเชิงมุม - สำหรับท่อและส่วนประกอบ
การตรวจจับการเปลี่ยนท่อ
ท่อไร้สัญชาติ / บริสุทธิ์
โดยค่าเริ่มต้นไปป์จะไร้สัญชาติ / บริสุทธิ์ ท่อไร้สัญชาติ / บริสุทธิ์เพียงแค่แปลงข้อมูลอินพุตเป็นข้อมูลเอาต์พุต พวกเขาจำอะไรไม่ได้ดังนั้นจึงไม่มีคุณสมบัติใด ๆ - เป็นเพียงtransform()
วิธีการ Angular จึงสามารถปรับการรักษาไปป์ไร้สัญชาติ / บริสุทธิ์ได้อย่างเหมาะสมที่สุด: หากปัจจัยการผลิตไม่เปลี่ยนแปลงท่อก็ไม่จำเป็นต้องดำเนินการในระหว่างรอบการตรวจจับการเปลี่ยนแปลง สำหรับท่อเช่น{{power | exponentialStrength: factor}}
, power
และfactor
เป็นปัจจัยการผลิต
สำหรับคำถามนี้"#student of students | sortByName:queryElem.value"
, students
และqueryElem.value
เป็นปัจจัยการผลิตและท่อsortByName
เป็นไร้สัญชาติ / บริสุทธิ์ students
คืออาร์เรย์ (การอ้างอิง)
- เมื่อมีการเพิ่มนักเรียนการอ้างอิงอาร์เรย์จะไม่เปลี่ยนแปลง -
students
ไม่เปลี่ยนแปลงดังนั้นจึงไม่มีการดำเนินการไปป์ไร้สัญชาติ / บริสุทธิ์
- เมื่อมีการพิมพ์บางสิ่งลงในอินพุตตัวกรอง
queryElem.value
จะมีการเปลี่ยนแปลงดังนั้นจึงมีการดำเนินการไปป์ไร้สัญชาติ / บริสุทธิ์
วิธีหนึ่งในการแก้ไขปัญหาอาร์เรย์คือการเปลี่ยนการอ้างอิงอาร์เรย์ทุกครั้งที่มีการเพิ่มนักเรียนกล่าวคือสร้างอาร์เรย์ใหม่ทุกครั้งที่มีการเพิ่มนักเรียน เราสามารถทำได้ด้วยconcat()
:
this.students = this.students.concat([{name: studentName}]);
แม้ว่าวิธีนี้จะได้ผล แต่addNewStudent()
วิธีการของเราก็ไม่ควรนำมาใช้ด้วยวิธีใดวิธีหนึ่งเพียงเพราะเราใช้ไปป์ เราต้องการใช้push()
เพื่อเพิ่มในอาร์เรย์ของเรา
ท่อสถานะ
ท่อที่มีสถานะมีสถานะ - โดยปกติจะมีคุณสมบัติไม่ใช่แค่transform()
วิธีการ พวกเขาอาจต้องได้รับการประเมินแม้ว่าปัจจัยการผลิตจะไม่เปลี่ยนแปลงก็ตาม เมื่อเราระบุว่าไปป์มีสถานะ / ไม่บริสุทธิ์ - pure: false
- เมื่อใดก็ตามที่ระบบตรวจจับการเปลี่ยนแปลงของ Angular ตรวจสอบส่วนประกอบสำหรับการเปลี่ยนแปลงและส่วนประกอบนั้นใช้ไปป์แบบ stateful ระบบจะตรวจสอบเอาต์พุตของไปป์ว่าอินพุตมีการเปลี่ยนแปลงหรือไม่
สิ่งนี้ดูเหมือนสิ่งที่เราต้องการแม้ว่าจะมีประสิทธิภาพน้อยกว่าเนื่องจากเราต้องการให้ไพพ์ทำงานแม้ว่าการstudents
อ้างอิงจะไม่เปลี่ยนแปลงก็ตาม หากเราทำให้ไปป์มีสถานะเป็นปกติเราจะได้รับข้อผิดพลาด:
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
ตามคำตอบของ @drewmoore "ข้อผิดพลาดนี้เกิดขึ้นในโหมด dev เท่านั้น (ซึ่งเปิดใช้งานโดยค่าเริ่มต้นเป็นเบต้า -0) หากคุณโทรหาenableProdMode()
เมื่อบูตเครื่องแอปข้อผิดพลาดจะไม่เกิดขึ้น" เอกสารสำหรับApplicationRef.tick()
รัฐ:
ในโหมดการพัฒนาขีด () ยังดำเนินการตรวจจับการเปลี่ยนแปลงรอบที่สองเพื่อให้แน่ใจว่าไม่มีการตรวจพบการเปลี่ยนแปลงเพิ่มเติม หากมีการเปลี่ยนแปลงเพิ่มเติมในรอบที่สองนี้การเชื่อมโยงในแอปจะมีผลข้างเคียงที่ไม่สามารถแก้ไขได้ในการตรวจจับการเปลี่ยนแปลงครั้งเดียว ในกรณีนี้ Angular จะแสดงข้อผิดพลาดเนื่องจากแอปพลิเคชัน Angular สามารถมีการตรวจจับการเปลี่ยนแปลงได้เพียงครั้งเดียวในระหว่างที่การตรวจจับการเปลี่ยนแปลงทั้งหมดจะต้องเสร็จสมบูรณ์
ในสถานการณ์ของเราฉันเชื่อว่าข้อผิดพลาดเป็นการหลอกลวง / ทำให้เข้าใจผิด เรามีท่อที่มีสถานะและเอาต์พุตสามารถเปลี่ยนแปลงได้ทุกครั้งที่มีการเรียกใช้ซึ่งอาจมีผลข้างเคียงและไม่เป็นไร NgFor ได้รับการประเมินหลังจากท่อดังนั้นจึงควรทำงานได้ดี
อย่างไรก็ตามเราไม่สามารถพัฒนาได้จริง ๆ เมื่อเกิดข้อผิดพลาดนี้ดังนั้นวิธีแก้ปัญหาอย่างหนึ่งคือการเพิ่มคุณสมบัติอาร์เรย์ (เช่นสถานะ) ให้กับการนำไปใช้งานไปป์และส่งคืนอาร์เรย์นั้นเสมอ ดูคำตอบของ @ pixelbits สำหรับโซลูชันนี้
อย่างไรก็ตามเราสามารถมีประสิทธิภาพมากขึ้นและอย่างที่เราเห็นเราไม่ต้องการคุณสมบัติอาร์เรย์ในการนำไปใช้งานไปป์และเราไม่จำเป็นต้องมีวิธีแก้ปัญหาสำหรับการตรวจจับการเปลี่ยนแปลงสองครั้ง
การตรวจจับการเปลี่ยนแปลงส่วนประกอบ
ตามค่าเริ่มต้นในทุกเหตุการณ์ของเบราว์เซอร์การตรวจจับการเปลี่ยนแปลงเชิงมุมจะตรวจสอบทุกองค์ประกอบเพื่อดูว่ามีการเปลี่ยนแปลงหรือไม่ - อินพุตและเทมเพลต (และอาจเป็นอย่างอื่น?)
ถ้าเรารู้ว่าส่วนประกอบขึ้นอยู่กับคุณสมบัติการป้อนข้อมูล (และเหตุการณ์เทมเพลต) เท่านั้นและคุณสมบัติการป้อนข้อมูลไม่เปลี่ยนรูปเราสามารถใช้onPush
กลยุทธ์การตรวจจับการเปลี่ยนแปลงที่มีประสิทธิภาพมากขึ้นได้ ด้วยกลยุทธ์นี้แทนที่จะตรวจสอบทุกเหตุการณ์ของเบราว์เซอร์คอมโพเนนต์จะถูกตรวจสอบเฉพาะเมื่ออินพุตเปลี่ยนไปและเมื่อเหตุการณ์ของเทมเพลตทริกเกอร์ และเห็นได้ชัดว่าเราไม่ได้รับExpression ... has changed after it was checked
ข้อผิดพลาดจากการตั้งค่านี้ เนื่องจากonPush
จะไม่มีการตรวจสอบส่วนประกอบอีกจนกว่าจะมีการ "ทำเครื่องหมาย" ( ChangeDetectorRef.markForCheck()
) อีกครั้ง ดังนั้นการเชื่อมเทมเพลตและเอาต์พุตไปป์ stateful จึงถูกดำเนินการ / ประเมินเพียงครั้งเดียว ท่อไร้สัญชาติ / บริสุทธิ์จะยังคงไม่ดำเนินการเว้นแต่ปัจจัยการผลิตจะเปลี่ยนไป ดังนั้นเรายังคงต้องการท่อที่มีสถานะตรงนี้
นี่คือวิธีแก้ปัญหาที่ @EricMartinez แนะนำ: ไปป์ stateful พร้อมonPush
การตรวจจับการเปลี่ยนแปลง ดูคำตอบของ @ caffinatedmonkey สำหรับโซลูชันนี้
โปรดทราบว่าด้วยวิธีนี้transform()
วิธีนี้ไม่จำเป็นต้องส่งคืนอาร์เรย์เดียวกันทุกครั้ง ฉันคิดว่ามันแปลกไปหน่อย: ไปป์ที่มีสถานะไม่มีสถานะ ลองคิดดูเพิ่มเติม ... ท่อสถานะน่าจะส่งคืนอาร์เรย์เดียวกันเสมอ มิฉะนั้นจะสามารถใช้ได้เฉพาะกับonPush
ส่วนประกอบในโหมด dev
หลังจากนั้นฉันคิดว่าฉันชอบการผสมผสานระหว่างคำตอบของ @ Eric และ @ pixelbits: ไปป์ stateful ที่ส่งคืนการอ้างอิงอาร์เรย์เดียวกันพร้อมonPush
การตรวจจับการเปลี่ยนแปลงหากส่วนประกอบอนุญาต เนื่องจากท่อ stateful onPush
กลับอ้างอิงอาร์เรย์เดียวกันท่อยังสามารถใช้กับชิ้นส่วนที่ไม่ได้รับการกำหนดค่าด้วย
Plunker
สิ่งนี้อาจจะกลายเป็นสำนวน Angular 2: ถ้าอาร์เรย์ป้อนไปป์และอาร์เรย์อาจเปลี่ยนแปลง (รายการในอาร์เรย์ที่ไม่ใช่การอ้างอิงอาร์เรย์) เราจำเป็นต้องใช้ไปป์ที่มีสถานะ
pure:false
ในท่อของคุณและchangeDetection: ChangeDetectionStrategy.OnPush
ในส่วนประกอบของคุณ