2017-06-20 62 views
3

我有兩個組件:ParentComponent > ChildComponent和一個服務,例如, TitleService更新父組件中父值的值會導致ExpressionChangedAfterItHasBeenCheckedError發生角度

ParentComponent看起來是這樣的:

export class ParentComponent implements OnInit, OnDestroy { 

    title: string; 


    private titleSubscription: Subscription; 


    constructor (private titleService: TitleService) { 
    } 


    ngOnInit(): void { 

    // Watching for title change. 
    this.titleSubscription = this.titleService.onTitleChange() 
     .subscribe(title => this.title = title) 
    ; 

    } 

    ngOnDestroy(): void { 
    if (this.titleSubscription) { 
     this.titleSubscription.unsubscribe(); 
    } 
    }  

} 

ChildComponent看起來是這樣的:

export class ChildComponent implements OnInit { 

    constructor (
    private route: ActivatedRoute, 
    private titleService: TitleService 
) { 
    } 


    ngOnInit(): void { 

    // Updating title. 
    this.titleService.setTitle(this.route.snapshot.data.title); 

    } 

} 

的想法很簡單:ParentController顯示標題在屏幕上。爲了始終呈現其訂閱TitleService的實際標題並監聽事件。當標題被改變時,事件發生並且標題被更新。

當加載ChildComponent時,它從路由器(它會動態解析)獲取數據並告知TitleService用新值更新標題。

問題是這樣的解決方案會導致此錯誤:

Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'Dynamic Title'.

它看起來像值在變化檢測一輪更新。

我是否需要重新安排代碼以實現更好的實現,還是必須在某處啓動另一個更改檢測?

  • 我可以移動setTitle()onTitleChange()調用推崇構造函數,但我讀過,它被認爲是一種不好的做法做任何「繁重工作」在構造邏輯,除了初始化局部性質。

  • 此外,標題應由子組件確定,因此無法從中提取此邏輯。


更新

我已經實現了一個小例子,以便更好地說明這個問題。你可以在the GitHub repository找到它。

<p>Parent Component</p> 

<p>Title: "<span *ngIf="title">{{ title }}</span>"</p> 

<hr> 

<app-child></app-child> 
+0

您是否嘗試使用ngAfterViewInit代替ParentComponent中的ngOnInit? –

+0

你可以顯示你的路線配置嗎? –

+0

還有組件模板? –

回答

8

文章Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError error很詳細地解釋了這種行爲。

有你的問題,有兩種可能的解決方案:

1)把app-childngIf

<app-child></app-child> 
<p>Title: "<span *ngIf="title">{{ title }}</span>"</p> 

2)使用異步事件:

export class TitleService { 
    private titleChangeEmitter = new EventEmitter<string>(true); 
                 ^^^^^^-------------- 

After thorough investigation the problem only occurred when using *ngIf="title"

的問題,你'描述不是特定於ngIf並且可以通過實現依賴於母公司的輸入之後輸入傳遞到一個指令變化檢測過程中被同步更新自定義指令很容易地複製:

@Directive({ 
    selector: '[aDir]' 
}) 
export class ADirective { 
    @Input() aDir; 

------------ 

<div [aDir]="title"></div> 
<app-child></app-child> <----------- will throw ExpressionChangedAfterItHasBeenCheckedError 

爲什麼實際情況,需要有很好的理解角度內部特定的變化檢測和組件/指令表示。你可以用這些文章開始:

雖然它不可能在這個答案來解釋一切細節,這裏是高層次的解釋。在digest cycle期間,Angular對子指令執行某些操作。其中一項操作是更新輸入並在子指令/組件上調用ngOnInit生命週期掛鉤。最重要的是,這些操作都是在嚴格的順序進行:

  1. 更新輸入
  2. 呼叫ngOnInit

現在你有以下層次:

parent-component 
    ng-if 
    child-component 

和角以下層次結構當執行上述操作時。因此,假設目前角檢查parent-component

  1. 更新title輸入上ng-if結合,將其設置爲初始值undefined
  2. 呼叫ngOnInitng-if
  3. 無需更新child-component
  4. 呼叫ngOnIntichild-component這改變標題Dynamic Titleparent-component

所以,我們最終在ng-if但是當變化更新屬性時,其中角傳下來title=undefined情況檢測結束,我們有title=Dynamic Titleparent-component。現在,Angular運行第二個摘要來驗證沒有變化。但是,當它與前一個摘要中當前值ng-if相比較時,它會檢測到更改並引發錯誤。

改變的ng-ifparent-component模板的順序和child-component將導致局勢當parent-component屬性將之前的ng-if角度更新特性進行更新。

+0

謝謝你進行徹底的調查。它確實有幫助。我從來沒有關於模板內部組件的實際順序。這說得通。但是,更改模板中組件的順序不是解決此問題的好方法。還有什麼我可以做的嗎?謝謝! –

+0

你可以異步設置標題,這將解決問題,[這裏是類似的問題](https://stackoverflow.com/a/44691880/2545680) –

+0

使用'ServiceTitle'中的EventEmitter異步真正有助於緩解這個問題。再次感謝。你可以將這個解決方案添加到答案本身嗎?我會很樂意接受它。這是我的頭銜服務代碼:https://gist.github.com/slavafomin/fd94cda8d63c1091e97e22ff6b7336cf –

1

你可以嘗試使用ChangeDetectorRef所以角將手動通知有變化,誤差不會拋出:

深入調查後在ParentComponent使用*ngIf="title"只時發生問題。
更改titleParentComponent致電ChangeDetectorRefdetectChanges方法。

export class ParentComponent implements OnInit, OnDestroy { 

    title: string; 
    private titleSubscription: Subscription; 

    constructor(private titleService: TitleService, private changeDetector: ChangeDetectorRef) { 
    } 

    public ngOnInit() { 
    this.titleSubscription = this.titleService.onTitleChange() 
     .subscribe(title => this.title = title); 

    this.changeDetector.detectChanges(); 
    } 

    public ngOnDestroy(): void { 
    if (this.titleSubscription 
    ) { 
     this.titleSubscription.unsubscribe(); 
    } 
    } 

} 
+0

tnks,你的替代作品對我來說就像魔術一樣! –

0

就我而言,我是改變我的數據 - 這答案需要你閱讀AngularInDepth.com消化cycle-- 的HTML水平內部解釋的狀態下,所有我需要做的就是改變辦法,我處理數據,象這樣:

<div>{{event.subjects.pop()}}</div> 

<div>{{event.subjects[0]}}</div> 

總結:代替彈出 - 返回並刪除數組的從而改變我的d狀態的最後一個元素ata--我使用正常的方式獲取我的數據而不更改其狀態從而防止發生異常。

相關問題