Để hiểu đầy đủ vấn đề và các giải pháp khả thi, chúng ta cần thảo luận về phát hiện thay đổi Angular - đối với đường ống và các thành phần.
Phát hiện thay đổi đường ống
Ống không trạng thái / tinh khiết
Theo mặc định, đường ống là không trạng thái / thuần túy. Các đường ống không trạng thái / thuần túy chỉ đơn giản là chuyển đổi dữ liệu đầu vào thành dữ liệu đầu ra. Họ không nhớ bất cứ điều gì, vì vậy họ không có bất kỳ thuộc tính nào - chỉ là một transform()
phương thức. Do đó, Angular có thể tối ưu hóa việc xử lý các đường ống không trạng thái / thuần túy: nếu đầu vào của chúng không thay đổi, các đường ống không cần thực hiện trong chu kỳ phát hiện thay đổi. Đối với một đường ống chẳng hạn như {{power | exponentialStrength: factor}}
, power
và factor
là đầu vào.
Đối với câu hỏi này "#student of students | sortByName:queryElem.value"
, students
và queryElem.value
là đầu vào, và đường ống sortByName
là không trạng thái / thuần túy. students
là một mảng (tham chiếu).
- Khi một sinh viên được thêm vào, tham chiếu mảng không thay đổi -
students
không thay đổi - do đó đường ống không trạng thái / thuần túy không được thực thi.
- Khi một cái gì đó được nhập vào đầu vào bộ lọc,
queryElem.value
không thay đổi, do đó đường ống không trạng thái / thuần túy được thực thi.
Một cách để khắc phục sự cố mảng là thay đổi tham chiếu mảng mỗi khi thêm học sinh - tức là tạo mảng mới mỗi khi thêm học sinh. Chúng tôi có thể làm điều này với concat()
:
this.students = this.students.concat([{name: studentName}]);
Mặc dù điều này hoạt động, nhưng addNewStudent()
phương pháp của chúng tôi không nên được triển khai theo một cách nhất định chỉ vì chúng tôi đang sử dụng một đường ống. Chúng tôi muốn sử dụng push()
để thêm vào mảng của chúng tôi.
Đường ống trạng thái
Các đường ống trạng thái có trạng thái - chúng thường có các thuộc tính, không chỉ là một transform()
phương thức. Chúng có thể cần được đánh giá ngay cả khi đầu vào của chúng không thay đổi. Khi chúng tôi chỉ định rằng một đường ống là trạng thái / không thuần túy - pure: false
- thì bất cứ khi nào hệ thống phát hiện thay đổi của Angular kiểm tra một thành phần để tìm các thay đổi và thành phần đó sử dụng một đường ống trạng thái, nó sẽ kiểm tra đầu ra của đường ống, liệu đầu vào của nó có thay đổi hay không.
Điều này nghe có vẻ giống như những gì chúng ta muốn, mặc dù nó kém hiệu quả hơn, vì chúng ta muốn đường ống thực thi ngay cả khi students
tham chiếu không thay đổi. Nếu chúng tôi chỉ làm cho đường ống ở trạng thái hoạt động, chúng tôi sẽ gặp lỗi:
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
Theo câu trả lời của @ drawmoore , "lỗi này chỉ xảy ra ở chế độ nhà phát triển (được bật theo mặc định kể từ phiên bản beta-0). Nếu bạn gọi enableProdMode()
khi khởi động ứng dụng, lỗi sẽ không xuất hiện." Các tài liệu choApplicationRef.tick()
tiểu bang:
Trong chế độ phát triển, tick () cũng thực hiện chu kỳ phát hiện thay đổi thứ hai để đảm bảo rằng không có thay đổi nào khác được phát hiện. Nếu các thay đổi bổ sung được chọn trong chu kỳ thứ hai này, các ràng buộc trong ứng dụng có các tác dụng phụ không thể giải quyết trong một lần phát hiện thay đổi. Trong trường hợp này, Angular đưa ra một lỗi, vì một ứng dụng Angular chỉ có thể có một lượt phát hiện thay đổi trong đó tất cả các phát hiện thay đổi phải hoàn thành.
Trong trường hợp của chúng tôi, tôi tin rằng lỗi là không có thật / gây hiểu lầm. Chúng tôi có một đường ống trạng thái và đầu ra có thể thay đổi mỗi khi nó được gọi - nó có thể có tác dụng phụ và điều đó không sao cả. NgFor được đánh giá sau đường ống, vì vậy nó sẽ hoạt động tốt.
Tuy nhiên, chúng tôi không thể thực sự phát triển với lỗi này được ném ra, vì vậy một giải pháp là thêm một thuộc tính mảng (tức là trạng thái) vào việc triển khai ống và luôn trả về mảng đó. Xem câu trả lời của @ pixelbits cho giải pháp này.
Tuy nhiên, chúng ta có thể hiệu quả hơn và như chúng ta thấy, chúng ta sẽ không cần thuộc tính mảng trong việc triển khai đường ống và chúng ta sẽ không cần một giải pháp thay thế để phát hiện thay đổi kép.
Phát hiện thay đổi thành phần
Theo mặc định, trên mọi sự kiện của trình duyệt, tính năng phát hiện thay đổi Angular sẽ đi qua mọi thành phần để xem nó có thay đổi hay không - các đầu vào và mẫu (và có thể là những thứ khác?) Được kiểm tra.
Nếu chúng ta biết rằng một thành phần chỉ phụ thuộc vào các thuộc tính đầu vào của nó (và các sự kiện mẫu) và các thuộc tính đầu vào là bất biến, chúng ta có thể sử dụng onPush
chiến lược phát hiện thay đổi hiệu quả hơn nhiều . Với chiến lược này, thay vì kiểm tra mọi sự kiện của trình duyệt, một thành phần chỉ được kiểm tra khi các đầu vào thay đổi và khi các sự kiện mẫu kích hoạt. Và, rõ ràng, chúng tôi không gặp Expression ... has changed after it was checked
lỗi đó với cài đặt này. Điều này là do một onPush
thành phần không được kiểm tra lại cho đến khi nó được "đánh dấu" ( ChangeDetectorRef.markForCheck()
) một lần nữa. Vì vậy, các ràng buộc Mẫu và đầu ra ống trạng thái chỉ được thực thi / đánh giá một lần. Các đường ống không trạng thái / thuần túy vẫn không được thực thi trừ khi đầu vào của chúng thay đổi. Vì vậy, chúng tôi vẫn cần một đường ống trạng thái ở đây.
Đây là giải pháp mà @EricMartinez đề xuất: đường ống trạng thái với tính onPush
năng phát hiện thay đổi. Xem câu trả lời của @ caffinatedmonkey cho giải pháp này.
Lưu ý rằng với giải pháp này, transform()
phương thức không cần trả về cùng một mảng mỗi lần. Tôi thấy điều đó hơi kỳ lạ: một đường ống trạng thái không có trạng thái. Suy nghĩ về nó một chút nữa ... đường ống trạng thái có thể luôn trả về cùng một mảng. Nếu không, nó chỉ có thể được sử dụng với onPush
các thành phần trong chế độ nhà phát triển.
Vì vậy, sau tất cả những điều đó, tôi nghĩ tôi thích sự kết hợp giữa câu trả lời của @ Eric và @ pixelbits: đường ống trạng thái trả về cùng một tham chiếu mảng, với tính onPush
năng phát hiện thay đổi nếu thành phần cho phép. Vì đường ống trạng thái trả về cùng một tham chiếu mảng, nên đường ống vẫn có thể được sử dụng với các thành phần không được định cấu hình với onPush
.
Plunker
Điều này có thể sẽ trở thành một thành ngữ Angular 2: nếu một mảng đang cung cấp một đường ống và mảng có thể thay đổi (các mục trong mảng, không phải là tham chiếu mảng), chúng ta cần sử dụng một đường ống trạng thái.
pure:false
vào Ống của bạn vàchangeDetection: ChangeDetectionStrategy.OnPush
trong Thành phần của bạn.