Angular 2-모델 변경 후 뷰가 업데이트되지 않음


129

몇 초마다 REST API를 호출하고 일부 JSON 데이터를 다시받는 간단한 구성 요소가 있습니다. 내 로그 문과 네트워크 트래픽에서 반환되는 JSON 데이터가 변경되고 내 모델이 업데이트되고 있지만보기는 변경되지 않습니다.

내 구성 요소는 다음과 같습니다.

import {Component, OnInit} from 'angular2/core';
import {RecentDetectionService} from '../services/recentdetection.service';
import {RecentDetection} from '../model/recentdetection';
import {Observable} from 'rxjs/Rx';

@Component({
    selector: 'recent-detections',
    templateUrl: '/app/components/recentdetection.template.html',
    providers: [RecentDetectionService]
})



export class RecentDetectionComponent implements OnInit {

    recentDetections: Array<RecentDetection>;

    constructor(private recentDetectionService: RecentDetectionService) {
        this.recentDetections = new Array<RecentDetection>();
    }

    getRecentDetections(): void {
        this.recentDetectionService.getJsonFromApi()
            .subscribe(recent => { this.recentDetections = recent;
             console.log(this.recentDetections[0].macAddress) });
    }

    ngOnInit() {
        this.getRecentDetections();
        let timer = Observable.timer(2000, 5000);
        timer.subscribe(() => this.getRecentDetections());
    }
}

그리고 내 견해는 다음과 같습니다.

<div class="panel panel-default">
    <!-- Default panel contents -->
    <div class="panel-heading"><h3>Recently detected</h3></div>
    <div class="panel-body">
        <p>Recently detected devices</p>
    </div>

    <!-- Table -->
    <table class="table" style="table-layout: fixed;  word-wrap: break-word;">
        <thead>
            <tr>
                <th>Id</th>
                <th>Vendor</th>
                <th>Time</th>
                <th>Mac</th>
            </tr>
        </thead>
        <tbody  >
            <tr *ngFor="#detected of recentDetections">
                <td>{{detected.broadcastId}}</td>
                <td>{{detected.vendor}}</td>
                <td>{{detected.timeStamp | date:'yyyy-MM-dd HH:mm:ss'}}</td>
                <td>{{detected.macAddress}}</td>
            </tr>
        </tbody>
    </table>
</div>

console.log(this.recentDetections[0].macAddress)recentDetections 객체가 업데이트되는 결과를 볼 수 있지만 페이지를 다시로드하지 않으면 뷰의 테이블이 변경되지 않습니다.

나는 내가 여기서 뭘 잘못하고 있는지보기 위해 고군분투하고있다. 누구든지 도울 수 있습니까?


코드를 더 깨끗하고 덜 복잡하게 만드는 것이 좋습니다. stackoverflow.com/a/48873980/571030
glukki

답변:


216

서비스의 코드가 어떻게 든 Angular의 영역을 벗어 났을 수 있습니다. 이로 인해 변경 감지가 중단됩니다. 이것은 작동합니다.

import {Component, OnInit, NgZone} from 'angular2/core';

export class RecentDetectionComponent implements OnInit {

    recentDetections: Array<RecentDetection>;

    constructor(private zone:NgZone, // <== added
        private recentDetectionService: RecentDetectionService) {
        this.recentDetections = new Array<RecentDetection>();
    }

    getRecentDetections(): void {
        this.recentDetectionService.getJsonFromApi()
            .subscribe(recent => { 
                 this.zone.run(() => { // <== added
                     this.recentDetections = recent;
                     console.log(this.recentDetections[0].macAddress) 
                 });
        });
    }

    ngOnInit() {
        this.getRecentDetections();
        let timer = Observable.timer(2000, 5000);
        timer.subscribe(() => this.getRecentDetections());
    }
}

변경 감지를 호출하는 다른 방법 은 Angular에서 수동으로 변경 감지 트리거를 참조하십시오.

변경 감지를 호출하는 다른 방법은 다음과 같습니다.

ChangeDetectorRef.detectChanges()

현재 구성 요소와 그 자식에 대한 변경 감지를 즉시 실행

ChangeDetectorRef.markForCheck()

다음에 Angular가 변경 감지를 실행할 때 현재 구성 요소를 포함합니다.

ApplicationRef.tick()

전체 애플리케이션에 대한 변경 감지 실행


9
또 다른 대안은 ChangeDetectorRef를 주입하고 호출하는 cdRef.detectChanges()대신 zone.run(). 이는 전체 구성 요소 트리에서 변경 감지를 실행하지 않기 때문에 더 효율적일 수 있습니다 zone.run().
Mark Rajcok

1
동의하다. detectChanges()변경 사항이 구성 요소 및 해당 하위 항목에 국한된 경우 에만 사용 하십시오. 나의 이해는 즉 detectChanges()구성 요소와 그 하위를 확인하지만, zone.run()ApplicationRef.tick()전체 구성 요소 트리를 확인합니다.
Mark Rajcok

2
정확히 내가 찾던 것을 .. 사용하는 것은 말도 안 @Input()되거나 효과가 없었습니다.
yorch

15
나는 우리가 왜이 모든 매뉴얼이 필요한지, 왜 앵귤러 2 신이 그렇게 필요하게되었는지 이해하지 못한다. @Input이 구성 요소가 실제로 변경되는 데이터에 의존하는 부모 구성 요소가 말한 내용에 실제로 의존한다는 것을 의미하지 않는 이유는 무엇입니까? 작동하지 않는다는 것은 매우 우아하지 않은 것 같습니다. 내가 무엇을 놓치고 있습니까?

13
나를 위해 일했습니다. 그러나 이것은 앵귤러 프레임 워크 개발자가 앵귤러 프레임 워크의 복잡성에 빠져 있다는 느낌을받는 또 다른 예입니다. 응용 프로그램 개발자로서 angular를 사용할 때 angular가이를 수행하는 방법과 위에서 질문 한 것과 같은 문제를 해결하는 방법을 이해하는 데 많은 시간을 소비해야합니다. 끔찍하다 !!
Karl

32

원래 @Mark Rajcok의 의견에 대한 답변이지만 ChangeDetectorRef를 사용하여 테스트되고 솔루션으로 작업 한 것으로 여기에 배치하고 싶습니다. 여기서 좋은 점을 봅니다.

또 다른 대안은 주입하는 것입니다 ChangeDetectorRef및 통화 cdRef.detectChanges()대신 zone.run(). 이는 전체 구성 요소 트리에서 변경 감지를 실행하지 않기 때문에 더 효율적일 수 있습니다 zone.run(). – Mark Rajcok

따라서 코드는 다음과 같아야합니다.

import {Component, OnInit, ChangeDetectorRef} from 'angular2/core';

export class RecentDetectionComponent implements OnInit {

    recentDetections: Array<RecentDetection>;

    constructor(private cdRef: ChangeDetectorRef, // <== added
        private recentDetectionService: RecentDetectionService) {
        this.recentDetections = new Array<RecentDetection>();
    }

    getRecentDetections(): void {
        this.recentDetectionService.getJsonFromApi()
            .subscribe(recent => { 
                this.recentDetections = recent;
                console.log(this.recentDetections[0].macAddress);
                this.cdRef.detectChanges(); // <== added
            });
    }

    ngOnInit() {
        this.getRecentDetections();
        let timer = Observable.timer(2000, 5000);
        timer.subscribe(() => this.getRecentDetections());
    }
}

편집 : .detectChanges()내부 subscibe 를 사용하면 파괴 된 뷰를 사용하려는 시도가 발생할 수 있습니다. detectChanges

이를 해결하려면 unsubscribe구성 요소를 파괴하기 전에 해야 하므로 전체 코드는 다음과 같습니다.

import {Component, OnInit, ChangeDetectorRef, OnDestroy} from 'angular2/core';

export class RecentDetectionComponent implements OnInit, OnDestroy {

    recentDetections: Array<RecentDetection>;
    private timerObserver: Subscription;

    constructor(private cdRef: ChangeDetectorRef, // <== added
        private recentDetectionService: RecentDetectionService) {
        this.recentDetections = new Array<RecentDetection>();
    }

    getRecentDetections(): void {
        this.recentDetectionService.getJsonFromApi()
            .subscribe(recent => { 
                this.recentDetections = recent;
                console.log(this.recentDetections[0].macAddress);
                this.cdRef.detectChanges(); // <== added
            });
    }

    ngOnInit() {
        this.getRecentDetections();
        let timer = Observable.timer(2000, 5000);
        this.timerObserver = timer.subscribe(() => this.getRecentDetections());
    }

    ngOnDestroy() {
        this.timerObserver.unsubscribe();
    }

}

this.cdRef.detectChanges (); 내 문제를 해결했습니다. 감사합니다!
mangesh

제안 해 주셔서 감사합니다. 여러 번 호출을 시도한 후 오류가 발생합니다. failure : Error : ViewDestroyedError : 파괴 된 뷰를 사용하려고합니다.
shivam srivastava

8

사용해보십시오 @Input() recentDetections: Array<RecentDetection>;

편집 :@Input() 중요한 이유는 typescript / javascript 파일의 값을 뷰 (html)에 바인딩하기 때문입니다. @Input()데코레이터로선언 된 값이 변경되면 뷰가 자체적으로 업데이트됩니다. 는 IF@Input()또는@Output()장식이 변경,ngOnChanges-event는 트리거 및 뷰는 새 값으로 자동으로 업데이트됩니다. @Input()의지가 값을 양방향으로 바인딩한다고 말할 수 있습니다.

자세한 내용 은이 링크에서 각도로 입력 : 용어집 을 검색 하십시오.

편집 : Angular 2 개발에 대해 더 많이 알게 된 후, 나는@Input()실제로 해결책이 아니라고생각했으며 의견에서 언급했듯이,

@Input() 데이터가 구성 요소 내의 코드에서 변경된 경우가 아니라 구성 요소 외부에서 데이터 바인딩에 의해 데이터가 변경된 경우에만 적용됩니다 (부모의 바인딩 된 데이터가 변경됨).

@ Günter의 대답을 살펴보면 문제에 대한 더 정확하고 올바른 해결책입니다. 나는 여전히이 대답을 여기에 보관할 것이지만 Günter의 대답을 올바른 대답으로 따르십시오.


2
그거였다. 나는 전에 @Input () 주석을 보았지만 항상 양식 입력과 연결했으며 여기에 필요하다고 생각한 적이 없습니다. 건배.
Matt Watson

1
@John 추가 설명에 감사드립니다. 그러나 추가 한 내용은 데이터가 구성 요소 내의 코드에서 변경된 경우가 아니라 구성 요소 외부에서 데이터 바인딩 (상위의 바인딩 된 데이터가 변경됨)에 의해 데이터가 변경된 경우에만 적용됩니다.
Günter Zöchbauer

@Input()키워드를 사용할 때 연결된 속성 바인딩이 있으며 해당 바인딩은 값이 뷰에서 업데이트되도록합니다. 값은 라이프 사이클 이벤트의 일부가됩니다. 이 게시물 에는 @Input()키워드 에 대한 추가 정보가 포함되어 있습니다
John

포기하겠습니다. @Input()여기에이 관련되어 있는지, 왜 있어야하는지, 질문이 충분한 정보를 제공하지 않는지 알 수 없습니다. 어쨌든 인내심에 감사드립니다 :)
Günter Zöchbauer

3
@Input()문제의 근원을 숨기는 해결 방법입니다. 문제는 영역 및 변경 감지와 관련이 있어야합니다.
magiccrafter

2

제 경우에도 비슷한 문제가있었습니다. 부모 구성 요소에서 호출하는 함수 내에서 뷰를 업데이트하고 있었고 부모 구성 요소에서 @ViewChild (NameOfMyChieldComponent)를 사용하는 것을 잊었습니다. 이 어리석은 실수 때문에 적어도 3 시간을 잃었습니다. 즉 : 이러한 방법 을 사용할 필요가 없습니다 .

  • ChangeDetectorRef.detectChanges ()
  • ChangeDetectorRef.markForCheck ()
  • ApplicationRef.tick ()

1
@ViewChild로 자식 구성 요소를 어떻게 업데이트했는지 설명해 주시겠습니까? 감사합니다
Lucas Colombo

나는, 부모는 렌더링되지 않은 자식 클래스의 메소드를 호출하고, 메모리에만 존재하는 것으로 의심
glukki

1

영역 및 변경 감지를 처리하는 대신 AsyncPipe가 복잡성을 처리하도록합니다. 이것은 관찰 가능한 구독, 구독 취소 (메모리 누수 방지) 및 변경 감지를 Angular 숄더에 적용합니다.

새로운 요청의 결과를 내보내는 관찰 가능하도록 클래스를 변경하십시오.

export class RecentDetectionComponent implements OnInit {

    recentDetections$: Observable<Array<RecentDetection>>;

    constructor(private recentDetectionService: RecentDetectionService) {
    }

    ngOnInit() {
        this.recentDetections$ = Observable.interval(5000)
            .exhaustMap(() => this.recentDetectionService.getJsonFromApi())
            .do(recent => console.log(recent[0].macAddress));
    }
}

AsyncPipe를 사용하도록보기를 업데이트합니다.

<tr *ngFor="let detected of recentDetections$ | async">
    ...
</tr>

이 걸릴 것입니다 방법으로 서비스를 만들기 위해 더 나은 것을 추가 할 interval인수를, 그리고 :

  • ( exhaustMap위의 코드에서와 같이 사용하여) 새 요청을 만듭니다 .
  • 요청 오류 처리
  • 오프라인 상태에서 브라우저가 새 요청을하지 못하도록합니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.