NgFor는 Angular2의 파이프로 데이터를 업데이트하지 않습니다.


113

이 시나리오에서는 다음을 사용하여보기에 학생 목록 (배열)을 표시합니다 ngFor.

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

다른 학생을 목록에 추가 할 때마다 업데이트되는 것이 멋집니다.

그러나, 나는 그것을 줄 때 pipe까지 filter학생의 이름으로,

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

필터링 학생 이름 필드에 무언가를 입력 할 때까지 목록을 업데이트하지 않습니다.

여기에 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
pure:false파이프와 changeDetection: ChangeDetectionStrategy.OnPush컴포넌트에 추가하십시오 .
에릭 마르티네즈

2
감사합니다 @EricMartinez. 효과가있다. 하지만 조금 설명해 주시겠습니까?
추 아들

2
또한 .test()필터 기능에서 사용하지 않는 것이 좋습니다 . 사용자가 같은 특별한 의미의 문자가 포함 된 문자열을 입력하면 그 이유로는 : *또는 +등 코드가 중단됩니다. .includes()사용자 지정 함수와 함께 쿼리 문자열을 사용 하거나 이스케이프 해야한다고 생각합니다 .
Eggy 2015

6
pure:false파이프를 상태 저장으로 추가 하고 만들면 문제가 해결됩니다. ChangeDetectionStrategy를 수정할 필요가 없습니다.
pixelbits

2
이것을 읽는 사람이라면 Angular Pipes에 대한 문서가 훨씬 좋아졌고 여기에서 논의 된 것과 동일한 내용을 많이 다룹니다. 확인 해봐.
0xcaff

답변:


153

문제와 가능한 해결책을 완전히 이해하려면 파이프 및 구성 요소에 대한 각도 변경 감지에 대해 논의해야합니다.

파이프 변경 감지

무 상태 / 순수 파이프

기본적으로 파이프는 상태 비 저장 / 순수입니다. Stateless / Pure 파이프는 단순히 입력 데이터를 출력 데이터로 변환합니다. 그들은 아무것도 기억하지 못하기 때문에 어떤 속성도 가지고 있지 않습니다 transform(). 단지 방법 일뿐 입니다. 따라서 Angular는 상태 비 저장 / 순수 파이프의 처리를 최적화 할 수 있습니다. 입력이 변경되지 않으면 변경 감지주기 동안 파이프를 실행할 필요가 없습니다. {{power | exponentialStrength: factor}}, power와 같은 파이프의 factor경우 입력입니다.

이 질문에 대해 "#student of students | sortByName:queryElem.value", students그리고 queryElem.value입력하고, 파이프 sortByName무 / 순수하다. students배열 (참조)입니다.

  • 학생이 추가되면 배열 참조 가 변경 students되지 않고 변경되지 않습니다. 따라서 상태 비 저장 / 순수 파이프가 실행되지 않습니다.
  • 필터 입력에 무언가를 입력하면 queryElem.value변경되므로 stateless / pure 파이프가 실행됩니다.

배열 문제를 해결하는 한 가지 방법은 학생이 추가 될 때마다 배열 참조를 변경하는 것입니다. 즉, 학생이 추가 될 때마다 새 배열을 만듭니다. 다음과 concat()같이 할 수 있습니다 .

this.students = this.students.concat([{name: studentName}]);

이것이 효과가 있지만, 우리 addNewStudent()가 파이프를 사용하고 있기 때문에 우리의 방법을 특정 방식으로 구현할 필요는 없습니다. push()배열에 추가하는 데 사용하고 싶습니다 .

상태 저장 파이프

상태 저장 파이프에는 상태가 있습니다. 일반적으로 transform()메서드 뿐만 아니라 속성이 있습니다 . 입력이 변경되지 않은 경우에도 평가해야 할 수 있습니다. 파이프가 상태 저장 / 비 순수라고 지정하면 pure: false-Angular의 변경 감지 시스템이 구성 요소의 변경 사항을 확인하고 해당 구성 요소가 상태 저장 파이프를 사용할 때마다 입력이 변경되었는지 여부에 관계없이 파이프의 출력을 확인합니다.

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의 답변 에 따르면 "이 오류는 개발 모드에서만 발생합니다 (베타 0부터 기본적으로 활성화 됨). enableProdMode()앱을 부트 스트랩 할 때 호출 하면 오류가 발생하지 않습니다." 에 대한 문서ApplicationRef.tick() 상태 :

개발 모드에서 tick ()은 추가 변경이 감지되지 않도록 두 번째 변경 감지주기를 수행합니다. 이 두 번째주기 중에 추가 변경이 선택되면 앱의 바인딩에는 단일 변경 감지 단계에서 해결할 수없는 부작용이 있습니다. 이 경우 Angular 응용 프로그램은 모든 변경 감지를 완료해야하는 하나의 변경 감지 패스 만 가질 수 있으므로 Angular에서 오류가 발생합니다.

우리의 시나리오에서는 오류가 가짜 / 오해의 소지가 있다고 생각합니다. 상태 저장 파이프가 있으며 호출 될 때마다 출력이 변경 될 수 있습니다. 부작용이있을 수 있으며 괜찮습니다. NgFor는 파이프 후에 평가되므로 잘 작동합니다.

그러나이 오류가 발생하여 개발할 수는 없으므로 한 가지 해결 방법은 파이프 구현에 배열 속성 (즉, 상태)을 추가하고 항상 해당 배열을 반환하는 것입니다. 이 솔루션에 대한 @pixelbits의 답변을 참조하십시오.

그러나 우리는 더 효율적일 수 있으며 앞으로 보게 될 파이프 구현에 배열 속성이 필요하지 않으며 이중 변경 감지에 대한 해결 방법이 필요하지 않습니다.

구성 요소 변경 감지

기본적으로 모든 브라우저 이벤트에서 Angular 변경 감지는 모든 구성 요소를 통해 변경되었는지 확인합니다. 입력 및 템플릿 (및 기타 항목)이 확인됩니다.

구성 요소가 입력 속성 (및 템플릿 이벤트)에만 의존하고 입력 속성이 변경 불가능하다는 것을 알고 있다면 훨씬 더 효율적인 onPush변경 감지 전략을 사용할 수 있습니다 . 이 전략을 사용하면 모든 브라우저 이벤트를 확인하는 대신 입력이 변경되고 템플릿 이벤트가 트리거 될 때만 구성 요소가 확인됩니다. 그리고 분명히이 Expression ... has changed after it was checked설정 에서는 오류가 발생 하지 않습니다 . 이는 onPush구성 요소가 다시 "표시"( ChangeDetectorRef.markForCheck()) 될 때까지 다시 확인되지 않기 때문 입니다. 따라서 템플릿 바인딩 및 상태 저장 파이프 출력은 한 번만 실행 / 평가됩니다. Stateless / Pure 파이프는 입력이 변경되지 않는 한 여전히 실행되지 않습니다. 따라서 여기에는 여전히 상태 저장 파이프가 필요합니다.

@EricMartinez가 제안한 솔루션은 onPush변경 감지 기능이있는 상태 저장 파이프입니다 . 이 솔루션에 대한 @caffinatedmonkey의 답변을 참조하십시오.

이 솔루션을 사용하면 transform()메서드가 매번 동일한 배열을 반환 할 필요가 없습니다. 나는 그것이 조금 이상하다는 것을 알았다 : 상태가없는 상태 저장 파이프. 좀 더 생각해 보면 stateful 파이프는 항상 같은 배열을 반환해야합니다. 그렇지 않으면 onPush개발 모드의 구성 요소 에서만 사용할 수 있습니다 .


그래서 결국 나는 @Eric과 @pixelbits의 대답의 조합을 좋아한다고 생각합니다. 동일한 배열 참조를 반환하는 상태 저장 파이프 onPush, 구성 요소가 허용하는 경우 변경 감지. 상태 저장 파이프는 동일한 배열 참조를 반환하므로 .NET Framework로 구성되지 않은 구성 요소와 함께 파이프를 계속 사용할 수 있습니다 onPush.

Plunker

이것은 아마도 Angular 2 관용구가 될 것입니다. 배열이 파이프를 공급하고 있고 배열이 변경 될 수있는 경우 (배열 참조가 아닌 배열의 항목) 상태 저장 파이프를 사용해야합니다.


문제에 대해 자세히 설명해 주셔서 감사합니다. 배열의 변경 감지가 같음 비교 대신 ID로 구현된다는 것은 이상합니다. Observable로 이것을 해결할 수 있습니까?
Martin Nowak

@MartinNowak, 배열이 Observable이고 파이프가 상태 비 저장인지 묻는 경우 ... 모르겠습니다. 시도하지 않았습니다.
Mark Rajcok

또 다른 해결책은 출력 배열이 이벤트 리스너를 사용하여 입력 배열의 변경 사항을 추적하는 순수 파이프입니다.
Tuupertunut

방금 Plunker를 분기했으며 onPush변경 감지 없이도 작동합니다. plnkr.co/edit/gRl0Pt9oBO6038kCXZPk?p=preview
Dan

27

에릭 마르티네스는 추가 의견에서 지적 pure: false당신을 Pipe장식하고 changeDetection: ChangeDetectionStrategy.OnPush당신에게 Component당신의 문제를 해결합니다 장식. 다음은 작동하는 플 런커입니다. 로 변경해 ChangeDetectionStrategy.Always도 작동합니다. 그 이유는 다음과 같습니다.

파이프에 대한 angular2 가이드에 따르면 :

파이프는 기본적으로 상태 비 저장입니다. 데코레이터 의 pure속성을 로 설정하여 파이프를 상태 저장으로 선언해야합니다 . 이 설정은 Angular의 변경 감지 시스템이 입력이 변경되었는지 여부에 관계없이 매 사이클마다이 파이프의 출력을 확인하도록 지시합니다.@Pipefalse

ChangeDetectionStrategy경우 기본적으로 모든 바인딩이 매주기마다 확인됩니다. 때 pure: false파이프가 추가되어, 나는 변화 검출 방법은에서로 변경 생각 CheckAlwaysCheckOnce성능상의 이유로. 를 사용하면 OnPush입력 속성이 변경되거나 이벤트가 트리거 될 때만 구성 요소에 대한 바인딩이 확인됩니다. 의 중요한 부분 인 변경 감지기에 대한 자세한 내용은 angular2다음 링크를 확인하세요.


" pure: false파이프가 추가되면 변경 감지 방법이 CheckAlways에서 CheckOnce로 변경된다고 생각합니다."-문서에서 인용 한 내용과 일치하지 않습니다. 내 이해는 다음과 같습니다. 상태 비 저장 파이프의 입력은 기본적으로 매 사이클마다 확인됩니다. 변경 사항이 있으면 파이프의 transform()메서드가 호출되어 출력을 (재) 생성합니다. 상태 저장 파이프의 transform()메서드는 매주기마다 호출되며 해당 출력의 변경 사항을 확인합니다. stackoverflow.com/a/34477111/215945 도 참조하십시오 .
Mark Rajcok

@MarkRajcok, 죄송합니다. 그렇습니다. 그렇다면 변경 감지 전략을 변경하는 이유는 무엇입니까?
0xcaff 2015

1
좋은 질문입니다. 변경 감지 전략이 변경되면 OnPush(예 : 구성 요소가 "불변"으로 표시됨) 상태 저장 파이프 출력이 "안정화"될 필요가 없습니다. 즉, transform()메서드가 한 번만 실행되는 것 같습니다 (아마 모두 구성 요소에 대한 변경 감지는 한 번만 실행됩니다). pixelbits에서 @의 답 것을 대비 transform()(pixelbits의 방법에 따라 여러 번 호출하고 안정화하는 다른 않음 ), 따라서 필요 같은 배열 기준을 사용한다.
Mark Rajcok 2015

@MarkRajcok 당신이 말한 것이 옳다면, 효율성 측면에서 이것은 아마도 더 나은 방법 일 것입니다.이 특별한 사용 사례 transform에서는 출력이 안정화 될 때까지 새 데이터가 여러 번 푸시 될 때만 호출 되기 때문 입니다.
0xcaff 2015

1
아마도 내 장황한 대답을 참조하십시오. 나는 각도 2. 변화 감지에 대한 자세한 설명서가 있었으면 좋겠어
마크 Rajcok

22

데모 Plunkr

ChangeDetectionStrategy를 변경할 필요가 없습니다. Stateful Pipe를 구현하면 모든 것이 작동 할 수 있습니다.

이것은 상태 저장 파이프입니다 (다른 변경 사항 없음).

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

[: 나는 인 OnPush로 변질의 changeDectection없이이 오류가 발생했습니다 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]
추 아들

1
SortByNamePipe의 지역 변수에 값을 저장 한 다음 @pixelbits 변환 함수에서 반환해야하는 이유를 예를 들어 볼 수 있습니까? 또한 changeDetetion.OnPush를 사용하여 변환 함수를 통해 Angular 사이클을 두 번, 그렇지 않은 경우 4 번
Chu Son

@ChuSon, 아마도 이전 버전의 로그를보고 계실 것입니다. 값 배열을 반환하는 대신 값 배열에 대한 참조 를 반환하고 있으며 변경 사항을 감지 할 수 있습니다. Pixelbits, 귀하의 답변이 더 의미가 있습니다.
0xcaff 2015

다른 독자의 이익을 위해 주석에서 위에서 언급 한 ChuSon 오류 및 지역 변수를 사용해야하는 필요성과 관련하여 또 다른 질문이 생성 되고 픽셀 비트로 답변되었습니다.
Mark Rajcok

19

로부터 각 문서

순수하고 불순한 파이프

파이프에는 순수와 불순의 두 가지 범주가 있습니다. 파이프는 기본적으로 순수합니다. 지금까지 본 모든 파이프는 순수했습니다. 순수 플래그를 false로 설정하여 파이프를 불순하게 만듭니다. 다음과 같이 FlyingHeroesPipe를 불순하게 만들 수 있습니다.

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

그 전에 순수 파이프부터 시작하여 순수와 불순의 차이점을 이해하십시오.

순수 파이프 Angular는 입력 값의 순수 변경을 감지 할 때만 순수 파이프를 실행합니다. 순수한 변경은 기본 입력 값 (문자열, 숫자, 부울, 기호) 또는 변경된 객체 참조 (날짜, 배열, 함수, 객체)에 대한 변경입니다.

Angular는 (복합) 개체 내의 변경 사항을 무시합니다. 입력 월을 변경하거나 입력 배열에 추가하거나 입력 개체 속성을 업데이트하는 경우 순수 파이프를 호출하지 않습니다.

이것은 제한적으로 보일 수 있지만 빠릅니다. 객체 참조 검사는 빠르기 때문에 차이에 대한 심층 검사보다 훨씬 빠릅니다. 따라서 Angular는 파이프 실행과 뷰 업데이트를 모두 건너 뛸 수 있는지 여부를 신속하게 결정할 수 있습니다.

이러한 이유로 변경 감지 전략을 사용할 수있는 경우 순수 파이프가 선호됩니다. 할 수 없을 때는 불순한 파이프를 사용할 수 있습니다.


대답은 정확하지만, 그것은 좋은 것입니다 게시 할 때 더 많은 노력을 비트
스테판

2

pure : false 대신. this.students = Object.assign ([], NEW_ARRAY);로 구성 요소의 값을 전체 복사하고 바꿀 수 있습니다. 여기서 NEW_ARRAY는 수정 된 배열입니다.

각도 6에서 작동하며 다른 각도 버전에서도 작동합니다.


0

해결 방법 : 생성자에서 파이프를 수동으로 가져오고이 파이프를 사용하여 변환 메서드를 호출합니다.

constructor(
private searchFilter : TableFilterPipe) { }

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

사실 넌 파이프도 필요 없어


0

파이프 추가 매개 변수에 추가하고 배열 변경 직후에 변경하십시오. 순수 파이프로도 목록이 새로 고쳐집니다.

항목의하자 | 파이프 : 매개 변수


0

이 사용 사례에서는 데이터 필터링을 위해 ts 파일의 파이프를 사용했습니다. 순수 파이프를 사용하는 것보다 성능면에서 훨씬 좋습니다. 다음과 같이 ts에서 사용하십시오.

import { YourPipeComponentName } from 'YourPipeComponentPath';

class YourService {

  constructor(private pipe: YourPipeComponentName) {}

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

0

불순한 파이프를 만드는 것은 성능에 많은 비용이 듭니다. 따라서 불순한 파이프를 생성하지 말고 데이터 변경시 데이터 사본을 생성하여 데이터 변수의 참조를 변경하고 원본 데이터 변수에 사본의 참조를 재 할당하십시오.

            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();
            } 
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.