NgFor không cập nhật dữ liệu với Pipe trong Angular2


114

Trong trường hợp này, tôi đang hiển thị danh sách học sinh (mảng) cho dạng xem với ngFor:

<li *ngFor="#student of students">{{student.name}}</li>

Thật tuyệt vời khi nó cập nhật bất cứ khi nào tôi thêm sinh viên khác vào danh sách.

Tuy nhiên, khi tôi cho nó một pipeđến filterbởi tên sinh viên,

<li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>

Nó không cập nhật danh sách cho đến khi tôi gõ một cái gì đó vào trường lọc tên sinh viên.

Đây là một liên kết đến plnkr .

Hello_world.html

<h1>Students:</h1>
<label for="newStudentName"></label>
<input type="text" name="newStudentName" placeholder="newStudentName" #newStudentElem>
<button (click)="addNewStudent(newStudentElem.value)">Add New Student</button>
<br>
<input type="text" placeholder="Search" #queryElem (keyup)="0">
<ul>
    <li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>
</ul>

sort_by_name_pipe.ts

import {Pipe} from 'angular2/core';

@Pipe({
    name: 'sortByName'
})
export class SortByNamePipe {

    transform(value, [queryString]) {
        // console.log(value, queryString);
        return value.filter((student) => new RegExp(queryString).test(student.name))
        // return value;
    }
}


14
Thêm pure:falsevào Ống của bạn và changeDetection: ChangeDetectionStrategy.OnPushtrong Thành phần của bạn.
Eric Martinez

2
Cảm ơn @EricMartinez. Nó hoạt động. Nhưng bạn có thể giải thích một chút được không?
Chu Sơn

2
Ngoài ra, tôi khuyên bạn KHÔNG nên sử dụng .test()trong chức năng bộ lọc của bạn. Đó là bởi vì, nếu người dùng nhập một chuỗi bao gồm các ký tự có nghĩa đặc biệt như: *hoặc +v.v. thì mã của bạn sẽ bị hỏng . Tôi nghĩ bạn nên sử dụng .includes()hoặc thoát chuỗi truy vấn với chức năng tùy chỉnh.
Eggy

6
Thêm pure:falsevà làm cho đường ống của bạn ở trạng thái hoạt động sẽ khắc phục được sự cố. Sửa đổi ChangeDetectionStrategy là không cần thiết.
pixelbits

2
Đối với bất kỳ ai đang đọc tài liệu này, tài liệu về Angular Pipes đã trở nên tốt hơn nhiều và đề cập đến nhiều điều tương tự được thảo luận ở đây. Kiểm tra nó ra.
0xcaff

Câu trả lời:


154

Để 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}}, powerfactorlà đầu vào.

Đối với câu hỏi này "#student of students | sortByName:queryElem.value", studentsqueryElem.valuelà đầu vào, và đường ống sortByNamelà không trạng thái / thuần túy. studentslà 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 - studentskhô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.valuekhô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 studentstham 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 onPushchiế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 checkedlỗi đó với cài đặt này. Điều này là do một onPushthà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 onPushnă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 onPushcá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 onPushnă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.


Cảm ơn bạn đã giải thích chi tiết vấn đề. Thật kỳ lạ mặc dù việc phát hiện thay đổi của các mảng được thực hiện dưới dạng danh tính thay vì so sánh bình đẳng. Có thể giải quyết điều này với Observable không?
Martin Nowak

@MartinNowak, nếu bạn đang hỏi liệu mảng có phải là mảng có thể quan sát được hay không và đường ống không trạng thái ... Tôi không biết, tôi chưa thử.
Mark Rajcok

Một giải pháp khác sẽ là một đường ống thuần túy trong đó mảng đầu ra đang theo dõi các thay đổi trong mảng đầu vào với trình nghe sự kiện.
Tuupertunut

Tôi vừa mới chia hai Plunker của bạn, và nó làm việc cho tôi mà không onPushphát hiện sự thay đổi plnkr.co/edit/gRl0Pt9oBO6038kCXZPk?p=preview
Dan

28

Như Eric Martinez đã chỉ ra trong các nhận xét, việc thêm pure: falsevào trình Pipetrang trí và trình trang trí changeDetection: ChangeDetectionStrategy.OnPushcủa bạn Componentsẽ khắc phục được sự cố của bạn. Đây là một plunkr đang hoạt động. Thay đổi thành ChangeDetectionStrategy.Always, cũng hoạt động. Đây là lý do tại sao.

Theo hướng dẫn angle2 trên đường ống :

Các đường ống là không trạng thái theo mặc định. Chúng ta phải khai báo một đường ống là trạng thái bằng cách đặt thuộc puretính của người @Pipetrang trí thành false. Cài đặt này cho hệ thống phát hiện thay đổi của Angular kiểm tra đầu ra của đường ống này mỗi chu kỳ, liệu đầu vào của nó có thay đổi hay không.

Đối với ChangeDetectionStrategy, theo mặc định, tất cả các ràng buộc được kiểm tra mỗi chu kỳ. Khi một pure: falseđường ống được thêm vào, tôi tin rằng phương pháp phát hiện thay đổi sẽ chuyển thành từ CheckAlwayssang CheckOncevì lý do hiệu suất. Với OnPush, các ràng buộc cho Thành phần chỉ được kiểm tra khi một thuộc tính đầu vào thay đổi hoặc khi một sự kiện được kích hoạt. Để biết thêm thông tin về công cụ phát hiện thay đổi, một phần quan trọng của angular2, hãy xem các liên kết sau:


"Khi một pure: falseđường ống được thêm vào, tôi tin rằng phương pháp phát hiện thay đổi sẽ chuyển từ CheckAlways thành CheckOnce" - điều đó không đồng ý với những gì bạn đã trích dẫn từ tài liệu. Sự hiểu biết của tôi như sau: đầu vào của đường ống không trạng thái được kiểm tra mỗi chu kỳ theo mặc định. Nếu có sự thay đổi, transform()phương thức của đường ống được gọi để (lại) tạo ra đầu ra. transform()Phương thức của một đường ống trạng thái được gọi là mọi chu kỳ và đầu ra của nó được kiểm tra để tìm các thay đổi. Xem thêm stackoverflow.com/a/34477111/215945 .
Mark Rajcok

@MarkRajcok, Rất tiếc, bạn nói đúng, nhưng nếu đúng như vậy, tại sao việc thay đổi chiến lược phát hiện thay đổi lại hoạt động?
0xcaff

1
Câu hỏi tuyệt vời. Có vẻ như khi chiến lược phát hiện thay đổi được thay đổi thành OnPush(tức là, thành phần được đánh dấu là "không thay đổi"), đầu ra đường ống trạng thái không phải "ổn định" - tức là, có vẻ như transform()phương pháp chỉ được chạy một lần (có thể là tất cả phát hiện thay đổi cho thành phần chỉ được chạy một lần). Đối chiếu điều đó với câu trả lời của @ pixelbits, nơi transform()phương thức được gọi nhiều lần và nó phải ổn định (theo câu trả lời khác của pixelbits ), do đó cần phải sử dụng cùng một tham chiếu mảng.
Mark Rajcok

@MarkRajcok Nếu những gì bạn nói là đúng, về mặt hiệu quả, đây có lẽ là cách tốt hơn, trong trường hợp sử dụng cụ thể này vì transformchỉ được gọi khi dữ liệu mới được đẩy thay vì nhiều lần cho đến khi đầu ra ổn định, phải không?
0xcaff

1
Có lẽ, hãy xem câu trả lời dài dòng của tôi. Tôi muốn có tài liệu hướng dẫn thêm về phát hiện sự thay đổi trong góc 2.
Đánh dấu Rajcok

22

Demo Plunkr

Bạn không cần thay đổi ChangeDetectionStrategy. Triển khai một Pipe trạng thái là đủ để mọi thứ hoạt động.

Đây là một đường ống trạng thái (không có thay đổi nào khác được thực hiện):

@Pipe({
  name: 'sortByName',
  pure: false
})
export class SortByNamePipe {
  tmp = [];
  transform (value, [queryString]) {
    this.tmp.length = 0;
    // console.log(value, queryString);
    var arr = value.filter((student)=>new RegExp(queryString).test(student.name));
    for (var i =0; i < arr.length; ++i) {
        this.tmp.push(arr[i]);
     }

    return this.tmp;
  }
}

Tôi gặp lỗi này mà không sửa đổi changeDectection thành onPush: [ plnkr.co/edit/j1VUdgJxYr6yx8WgzCId?p=preview](Plnkr) Expression 'students | sortByName:queryElem.value in HelloWorld@7:6' has changed after it was checked. Previous value: '[object Object],[object Object],[object Object]'. Current value: '[object Object],[object Object],[object Object]' in [students | sortByName:queryElem.value in HelloWorld@7:6]
Chu Son

1
Bạn có thể ví dụ tại sao bạn cần lưu trữ các giá trị vào một biến cục bộ trong SortByNamePipe và sau đó trả lại nó trong hàm biến đổi @pixelbits không? Tôi cũng nhận thấy rằng chu kỳ góc thông qua chuyển đổi chức năng hai lần với changeDetetion.OnPush và 4 lần mà không có nó
Chu Sơn

@ChuSon, có thể bạn đang xem nhật ký từ phiên bản trước. Thay vì trả về một mảng giá trị, chúng tôi đang trả về một tham chiếu đến một mảng giá trị, tôi tin rằng thay đổi này có thể được phát hiện. Pixelbits, câu trả lời của bạn có ý nghĩa hơn.
0xcaff

Vì lợi ích của những độc giả khác, liên quan đến lỗi mà ChuSon đã đề cập ở trên trong các nhận xét và nhu cầu sử dụng biến cục bộ, một câu hỏi khác đã được tạo ra và được trả lời bởi pixelbits.
Mark Rajcok

19

Từ tài liệu góc

Đường ống tinh khiết và không tinh khiết

Có hai loại ống: tinh khiết và không tinh khiết. Các đường ống là nguyên chất theo mặc định. Mọi đường ống bạn đã thấy cho đến nay đều là tinh khiết. Bạn làm cho một đường ống không tinh khiết bằng cách đặt cờ thuần túy của nó thành false. Bạn có thể làm cho FlyingHeroesPipe trở nên không tinh khiết như thế này:

@Pipe({ name: 'flyingHeroesImpure', pure: false })

Trước khi làm điều đó, hãy hiểu sự khác biệt giữa tinh khiết và không tinh khiết, bắt đầu với một đường ống tinh khiết.

Đường ống thuần túy Angular chỉ thực hiện đường ống thuần túy khi nó phát hiện ra sự thay đổi thuần túy đối với giá trị đầu vào. Một thay đổi thuần túy là một sự thay đổi đối với giá trị đầu vào nguyên thủy (Chuỗi, Số, Boolean, Biểu tượng) hoặc một tham chiếu đối tượng đã thay đổi (Ngày, Mảng, Hàm, Đối tượng).

Angular bỏ qua những thay đổi bên trong các đối tượng (composite). Nó sẽ không gọi là đường ống thuần túy nếu bạn thay đổi tháng đầu vào, thêm vào mảng đầu vào hoặc cập nhật thuộc tính đối tượng đầu vào.

Điều này có vẻ hạn chế nhưng nó cũng nhanh chóng. Kiểm tra tham chiếu đối tượng diễn ra nhanh chóng — nhanh hơn nhiều so với kiểm tra sâu về sự khác biệt — vì vậy Angular có thể nhanh chóng xác định xem nó có thể bỏ qua cả quá trình thực thi đường ống và cập nhật chế độ xem hay không.

Vì lý do này, một đường ống thuần túy sẽ thích hợp hơn khi bạn có thể sống với chiến lược phát hiện thay đổi. Khi bạn không thể, bạn có thể sử dụng đường ống không tinh khiết.


Câu trả lời đúng, nhưng cố gắng hơn một chút khi đăng nó sẽ tốt
Stefan

2

Thay vì làm pure: false. Bạn có thể sao chép sâu và thay thế giá trị trong thành phần bằng this.students = Object.assign ([], NEW_ARRAY); trong đó NEW_ARRAY là mảng đã sửa đổi.

Nó hoạt động cho góc 6 và cũng sẽ hoạt động cho các phiên bản góc cạnh khác.


0

Một giải pháp thay thế: Nhập thủ công Pipe trong hàm tạo và phương pháp chuyển đổi cuộc gọi bằng cách sử dụng đường dẫn này

constructor(
private searchFilter : TableFilterPipe) { }

onChange() {
   this.data = this.searchFilter.transform(this.sourceData, this.searchText)}

Trên thực tế, bạn thậm chí không cần một đường ống


0

Thêm vào tham số bổ sung của ống dẫn và thay đổi nó ngay sau khi thay đổi mảng và ngay cả với ống dẫn thuần túy, danh sách sẽ được làm mới

để mục của các mặt hàng | đường ống: param


0

Trong trường hợp sử dụng này, tôi đã sử dụng tệp Pipe trong ts để lọc dữ liệu. Nó tốt hơn nhiều cho hiệu suất, so với sử dụng ống đơn thuần. Sử dụng trong ts như thế này:

import { YourPipeComponentName } from 'YourPipeComponentPath';

class YourService {

  constructor(private pipe: YourPipeComponentName) {}

  YourFunction(value) {
    this.pipe.transform(value, 'pipeFilter');
  }
}

0

để tạo ra các đường ống không tinh khiết là tốn kém về hiệu suất. do đó, không tạo các đường ống không tinh khiết, thay vào đó thay đổi tham chiếu của biến dữ liệu bằng cách tạo bản sao dữ liệu khi thay đổi dữ liệu và gán lại tham chiếu của bản sao trong biến dữ liệu gốc.

            emp=[];
            empid:number;
            name:string;
            city:string;
            salary:number;
            gender:string;
            dob:string;
            experience:number;

            add(){
              const temp=[...this.emps];
              const e={empid:this.empid,name:this.name,gender:this.gender,city:this.city,salary:this.salary,dob:this.dob,experience:this.experience};
              temp.push(e); 
              this.emps =temp;
              //this.reset();
            } 
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.