Angular2 변경 감지 : 중첩 된 객체에 대해 ngOnChanges가 실행되지 않음


129

나는 이것에 대해 처음으로 질문하지는 않지만 이전 질문에서 답을 찾을 수 없다는 것을 알고 있습니다. 나는 이것을 하나의 구성 요소로 가지고 있습니다.

<div class="col-sm-5">
    <laps
        [lapsData]="rawLapsData"
        [selectedTps]="selectedTps"
        (lapsHandler)="lapsHandler($event)">
    </laps>
</div>

<map
    [lapsData]="rawLapsData"
    class="col-sm-7">
</map>

컨트롤러 rawLapsdata에서 때때로 변경됩니다.

에서 laps, 데이터는 표 형식으로 HTML로서 출력된다. 변경 될 때마다 rawLapsdata변경됩니다.

map구성 요소의 요구에 사용하는 ngOnChanges구글지도에 마커를 다시 그리기 트리거로. 문제는 rawLapsData부모 가 변경 될 때 ngOnChanges가 실행되지 않는다는 것 입니다. 어떡해?

import {Component, Input, OnInit, OnChanges, SimpleChange} from 'angular2/core';

@Component({
    selector: 'map',
    templateUrl: './components/edMap/edMap.html',
    styleUrls: ['./components/edMap/edMap.css']
})
export class MapCmp implements OnInit, OnChanges {
    @Input() lapsData: any;
    map: google.maps.Map;

    ngOnInit() {
        ...
    }

    ngOnChanges(changes: { [propName: string]: SimpleChange }) {
        console.log('ngOnChanges = ', changes['lapsData']);
        if (this.map) this.drawMarkers();
    }

업데이트 : ngOnChanges가 작동하지 않지만 lapsData가 업데이트되는 것처럼 보입니다. ngInit에는 줌 변경을위한 이벤트 리스너가 있으며this.drawmarkers . 줌을 변경하면 실제로 마커가 변경되는 것을 볼 수 있습니다. 따라서 유일한 문제는 입력 데이터가 변경 될 때 알림을받지 못한다는 것입니다.

부모님에게는이 줄이 있습니다. 변경 사항은 랩에는 반영되지만 맵에는 반영되지 않습니다.

this.rawLapsData = deletePoints(this.rawLapsData, this.selectedTps);

그리고 this.rawLapsData그 자체가 큰 json 객체의 중간을 가리키는 포인터입니다.

this.rawLapsData = this.main.data.TrainingCenterDatabase.Activities[0].Activity[0].Lap;

코드는 데이터가 어떻게 업데이트되는지 또는 어떤 유형의 데이터인지 보여주지 않습니다. 새 인스턴스가 할당되었거나 값의 속성 만 수정 되었습니까?
Günter Zöchbauer

@ GünterZöchbauer 부모 컴포넌트의 라인을 추가했습니다
Simon H

이 줄을 zone.run(...)감싸면 그렇게해야합니다.
Günter Zöchbauer

1
배열 (참조)이 변경 ngOnChanges()되지 않으므로 호출되지 않습니다. ngDoCheck()자신의 논리를 사용 하고 구현하여 배열 내용이 변경되었는지 확인할 수 있습니다 . lapsData와 같은 배열에 대한 참조가 있기 때문에 업데이트됩니다 rawLapsData.
Mark Rajcok

1
1) 랩 구성 요소에서 코드 / 템플릿은 lapsData 배열의 각 항목을 반복하고 내용을 표시하므로 각 데이터 조각에 각도 바인딩이 표시됩니다. 2) Angular가 구성 요소의 입력 속성에 대한 변경 사항 (참조 확인)을 감지하지 않더라도 여전히 기본적으로 모든 템플릿 바인딩을 확인합니다. 랩에서 변경 사항을 확인하는 방법입니다. 3)지도 구성 요소의 템플릿에 lapsData 입력 속성에 대한 바인딩이 없을 것입니다. 그것은 차이점을 설명 할 것입니다.
Mark Rajcok

답변:


153

rawLapsData 배열의 내용을 수정하더라도 (예 : 항목 추가, 항목 제거, 항목 변경)에도 동일한 배열을 계속 가리 킵니다.

변경 감지 중에 Angular는 구성 요소의 입력 속성에서 변경 사항을 검사 할 때 (필수적으로) ===더티 검사에 사용합니다. 배열의 경우 이는 배열 참조 (전용)가 더티 검사됨을 의미합니다. 때문에 rawLapsData배열 참조는 변경되지 않고, ngOnChanges()호출되지 않을 것이다.

두 가지 가능한 솔루션을 생각할 수 있습니다.

  1. ngDoCheck()배열 내용이 변경되었는지 확인하기 위해 자체 변경 감지 논리를 구현 하고 수행하십시오. (Lifecycle Hooks 문서에는 예제가 있습니다.)

  2. rawLapsData배열 내용을 변경할 때마다 새 배열을 할당하십시오 . 그러면 ngOnChanges()배열 (참조)이 변경으로 표시되므로 호출됩니다.

당신의 대답에서, 당신은 다른 해결책을 생각해 냈습니다.

OP에 대한 의견을 반복합니다.

여전히 laps변경 사항을 가져올 수 있는 방법을 보지 못했습니다 (확실히ngOnChanges() 자신 합니까?) map.

  • 에서 laps구성 요소 코드 / 된 템플릿의 각 항목을 통해 루프 lapsData배열 및 표시 내용 때문에 표시되는 데이터의 각 부분에 각도 바인딩이있다.
  • Angular가 구성 요소의 입력 속성에 대한 변경 사항을 감지하지 않더라도 ( ===확인 사용 ) 여전히 기본적으로 모든 템플릿 바인딩을 더티 확인합니다. 이 중 하나라도 변경되면 Angular는 DOM을 업데이트합니다. 그것이 당신이보고있는 것입니다.
  • maps구성 요소는 가능성의에 해당 템플리트의 모든 바인딩이없는 lapsData입력 속성을, 맞죠? 그것은 차이점을 설명 할 것입니다.

참고로 lapsData두 구성 요소와 rawLapsData동일한 / 하나의 어레이에 대한 모든 포인트 부모 구성 요소이다. 따라서 Angular가 lapsData입력 속성에 대한 (참조) 변경 사항을 인식하지 않더라도 구성 요소는 하나의 배열을 공유 / 참조하기 때문에 "get"/ 배열 내용 변경을 볼 수 있습니다. 기본 유형 (문자열, 숫자, 부울)에서와 같이 이러한 변경 사항을 전파하기 위해 Angular가 필요하지 않습니다. 그러나 기본 유형의 경우 값을 변경하면 항상 트리거됩니다 ngOnChanges(). 이는 답변 / 솔루션에서 악용하는 것입니다.

아마 지금까지 알아 낸 것처럼 객체 입력 속성은 배열 입력 속성과 동일한 동작을 갖습니다.


그렇습니다. 깊게 중첩 된 객체를 변경하고 있었기 때문에 Angular가 구조의 변화를 발견하기가 어려워졌습니다. 변경을 선호하지는 않지만 이것은 XML로 변환되며 마지막에 다시 XML을 다시 만들고 싶을 때 주변 데이터를 잃을 여유가 없습니다.
Simon H

7
@SimonH, "Angular가 구조의 변화를 발견하기 어렵습니다"-Angular는 배열이나 객체 인 입력 속성 내부에서도 변경 사항을 확인하지 않습니다. 객체와 배열의 경우 값이 변경되었는지 확인합니다. 기본 유형의 경우 값은 ... 값입니다. (나는 모든 전문 용어가 똑바로 확실하지 않지만 당신은 아이디어를 얻는다.)
Mark Rajcok

11
좋은 대답입니다. Angular2 팀은 변경 감지 내부에 대한 상세하고 권위있는 문서를 필사적으로 게시해야합니다.

doCheck에서 기능을 수행하면 내 경우에는 do check가 너무 많이 호출됩니다. 다른 방법을 말씀해 주시겠습니까?
Mr_Perfect

@MarkRajcok이 문제를 해결하도록 도와 주시겠습니까? stackoverflow.com/questions/50166996/…
Nikson

27

가장 깨끗한 접근 방식은 아니지만 값을 변경할 때마다 객체를 복제 할 수 있습니까?

   rawLapsData = Object.assign({}, rawLapsData);

나는 당신 자신을 구현하는 것 ngDoCheck()보다이 접근법을 선호 하지만 @ GünterZöchbauer와 같은 누군가가 차임 할 수 있다고 생각합니다 .


대상 브라우저가 <Object.assign ()>을 지원하는지 확실하지 않으면 json 문자열로 문자열 화하고 다시 json으로 구문 분석하여 새 오브젝트를 작성할 수도 있습니다.
Guntram

1
@ 건 트람 또는 폴리 필?
David

12

배열의 경우 다음과 같이 할 수 있습니다.

에서 .ts파일 (부모 요소) 여기서 당신은 당신을 업데이트 rawLapsData이런 식으로 작업을 수행합니다

rawLapsData = somevalue; // change detection will not happen

해결책:

rawLapsData = {...somevalue}; //change detection will happen

ngOnChanges하위 구성 요소에서 호출됩니다


10

Mark Rajcok의 두 번째 솔루션 확장

배열 내용을 변경할 때마다 rawLapsData에 새 배열을 할당하십시오. 그러면 배열 (참조)이 변경으로 표시되므로 ngOnChanges ()가 호출됩니다.

다음과 같이 배열의 내용을 복제 할 수 있습니다.

rawLapsData = rawLapsData.slice(0);

나는 이것을 언급하고 있기 때문에

rawLapsData = Object.assign ({}, rawLapsData);

나를 위해 일하지 않았다. 이게 도움이 되길 바란다.


8

데이터가 외부 라이브러리에서 온 경우 데이터 업데이트 명령문을에서 실행해야 할 수도 있습니다 zone.run(...). zone: NgZone이것을 주입 하십시오. zone.run()이미 외부 라이브러리의 인스턴스화를 실행할 수 있으면 zone.run()나중에 필요하지 않을 수 있습니다 .


영업에 코멘트에 언급 한 바와 같이, 변화는 JSON 개체 내에서 외부하지만 깊은하지 않았다
사이먼 H

1
귀하의 답변에서 알 수 있듯이 여전히 angular1과 같이 Angular2에서 동기화를 유지하기 위해 무언가를 실행해야 $scope.$apply합니까?
Pankaj Parkar

1
Angular 외부에서 무언가가 시작되면 영역으로 패치 된 API가 사용되지 않으며 Angular는 가능한 변경 사항에 대한 알림을받지 않습니다. 네, zone.run비슷합니다 $scope.apply.
Günter Zöchbauer 2016 년

6

사용은 ChangeDetectorRef.detectChanges()당신이 (그것의 더러운 검사로 그리워 있음) 중첩 된 개체를 편집 할 때 변경 감지를 실행하는 각도를 알 수 있습니다.


그러나 어떻게? 이것을 사용하려고합니다. 부모가 새 항목을 두 가지 방식으로 묶은 후 [(Collection)] 변경 감지를 트리거하려고합니다.
Deunz

문제는 변경 감지가 발생하지 않지만 변경 감지가 변경을 감지하지 않는다는 것입니다! 그래서 이것은 해결책이 아닌 것 같습니다! 이 답변에 5 개의 공감대가있는 이유는 무엇입니까?
Shahryar Saljoughi

5

문제를 해결하기위한 2 가지 해결책이 있습니다

  1. 데이터 변경 여부 ngDoCheck를 감지하는 object데 사용
  2. 상위 구성 요소에서 object새 메모리 주소를 지정하십시오 object = Object.create(object).

2
Object.create (object)와 Object.assign ({}, object) 사이에 눈에 띄는 차이가 있습니까?
Daniel Galarza

3

내 '해킹'솔루션은

   <div class="col-sm-5">
        <laps
            [lapsData]="rawLapsData"
            [selectedTps]="selectedTps"
            (lapsHandler)="lapsHandler($event)">
        </laps>
    </div>
    <map
        [lapsData]="rawLapsData"
        [selectedTps]="selectedTps"   // <--------
        class="col-sm-7">
    </map>

selectedTps는 rawLapsData와 동시에 변경되며 더 간단한 객체 기본 유형을 통해 변경을 감지 할 수있는 또 다른 기회를 맵에 제공 합니다. 우아하지는 않지만 작동합니다.


템플릿 구문, 특히 중대형 앱에서 다양한 구성 요소의 모든 변경 사항을 추적하기가 어렵습니다. 일반적으로 공유 이벤트 이미 터 및 구독을 사용하여 데이터 (빠른 솔루션)를 전달하거나이를 위해 Rux.Subject를 통해 Redux 패턴을 구현합니다 (계획 시간이있을 때) ...
Sasxa

3

개체 (내포 된 개체 포함)의 속성을 변경하면 변경 감지가 트리거되지 않습니다. 한 가지 해결책은 'lodash'clone () 함수를 사용하여 새 객체 참조를 다시 할당하는 것입니다.

import * as _ from 'lodash';

this.foo = _.clone(this.foo);

2

여기에 문제가있는 해킹이 있습니다.

따라서 OP와 비슷한 시나리오-데이터를 전달 해야하는 중첩 된 Angular 구성 요소가 있지만 입력은 배열을 가리키고 위에서 언급했듯이 Angular는 검사하지 않으므로 변경 사항을 보지 않습니다. 배열의 내용.

그래서 그것을 고치기 위해 배열을 Angular가 문자열로 변환하여 변경 사항을 감지 한 다음 중첩 된 구성 요소에서 문자열을 배열로 다시 분할하고 행복한 날을 다시 나눕니다.


1
왝! array.slice (0) 접근 방식이 훨씬 깔끔합니다.
Chris Haines

2

나는 같은 필요에 걸려 넘어졌다. 그리고 나는 이것에 대해 많이 읽었습니다. 여기 주제에 대한 구리가 있습니다.

푸시시 변경 감지를 원한다면 오른쪽의 객체 값을 변경할 때 변경 감지가 필요합니까? 그리고 어떻게 든 객체를 제거하면 그것을 가질 것입니다.

이미 말했듯이 changeDetectionStrategy.onPush 사용

changeDetectionStrategy.onPush를 사용하여이 구성 요소를 만들었다 고 가정 해보십시오.

<component [collection]="myCollection"></component>

그런 다음 항목을 푸시하고 변경 감지를 트리거합니다.

myCollection.push(anItem);
refresh();

또는 항목을 제거하고 변경 감지를 시작하십시오.

myCollection.splice(0,1);
refresh();

또는 항목의 속성 값을 변경하고 변경 감지를 트리거합니다.

myCollection[5].attribute = 'new value';
refresh();

새로 고침의 내용 :

refresh() : void {
    this.myCollection = this.myCollection.slice();
}

slice 메서드는 정확히 동일한 Array를 반환하고 [=] 기호는 새로운 참조를 만들어 필요할 때마다 변경 감지를 트리거합니다. 쉽고 읽기 쉬운 :)

문안 인사,


2

나는 여기에 언급 된 모든 해결책을 시도했지만 어떤 이유로 든 ngOnChanges()여전히 나를 위해 발사하지 않았습니다. 그래서 this.ngOnChanges()배열을 다시 채우는 서비스를 호출 한 후 호출했습니다. 아마 아닐 것입니다. 산뜻한? 지옥 아니 공장? 예!


1

확인을 위해 내 해결책은 다음과 같습니다.

this.arrayWeNeed.DoWhatWeNeedWithThisArray();
const tempArray = [...arrayWeNeed];
this.arrayWeNeed = [];
this.arrayWeNeed = tempArray;

그리고 이것은 ngOnChanges를 트리거합니다.


0

나는 그것을 위해 핵을 만들어야했다.

I created a Boolean Input variable and toggled it whenever array changed, which triggered change detection in the child component, hence achieving the purpose

0

내 경우에는 ngOnChange캡처하지 않은 객체 값이 변경되었습니다 . API 호출에 대한 응답으로 일부 객체 값이 수정됩니다. 객체를 다시 초기화하면 문제가 해결 ngOnChange되어 하위 구성 요소에서 트리거가 발생했습니다 .

같은 것

 this.pagingObj = new Paging(); //This line did the magic
 this.pagingObj.pageNumber = response.PageNumber;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.