2016-02-14 98 views
27

我試圖在使用Observables旁邊的ChangeDetectionStrategy.OnPush時,圍繞最佳實踐環繞我的頭。Angular 2中的ChangeDetectionStrategy.OnPush和Observable.subscribe

的例子演示了想要表現出一定的加載消息(或者簡單的微調動畫也許)的常見場景:

Plnkr here

@Component({ 
    selector: 'my-app', 
    template: `Are we loading?: {{loadingMessage}}`, 

    // Obviously "Default" will notice the change in `loadingMessage`... 
    // changeDetection: ChangeDetectionStrategy.Default 

    // But what is best practice when using OnPush? 
    changeDetection: ChangeDetectionStrategy.OnPush 
}) 
export class App implements OnInit { 

    private loadingMessage = "Wait for it..."; 

    constructor() { 

    } 

    ngOnInit() { 

    // Pretend we're loading data from a service. 
    // This component is only interested in the call's success 
    Observable.of(true) 
     .delay(2000) 
     .subscribe(success => { 

     if(success){ 
      console.log('Pretend loading: success!'); 

      // OnPush won't detect this change 
      this.loadingMessage = 'Success!'; 
     } 

     }); 

    } 
} 

我或多或少理解具有不變性的要求OnPush,對我而言,至少在討論實際模型數據(可能在某種商店中持有)時它是有意義的。

所以,我有兩個問題:

  1. 爲什麼沒有新的字符串值的分配'Success!'觸發這種改變探測器?就不變性而言,價值已經改變了,對嗎?
  2. 使用ChangeDetectionStrategy.OnPush時應該如何實現輕量級內部組件狀態(即loadingMessage)?如果有多種最佳實踐,請指出正確的方向。

回答

39

好問題。我有兩個報價從Savkin約onPush(因爲Angular.io文檔似乎沒有對這個話題的任何信息尚未):

該框架將檢查,只有當它們的輸入改變OnPush部件或組件的模板發射事件。 - ref

當使用OnPush時,Angular將只檢查組件的任何輸入屬性發生變化,觸發事件時,或者當observable觸發事件時。 - ref(在@vivainio的評論回覆中)

第二個引用看起來更加完整。 (太糟糕了,它被埋葬在一個評論!)

爲什麼沒有新的字符串值的分配Success!觸發 變化檢測?就不變性而言,價值已經改變了,對嗎?

OnPush不變性是指輸入屬性,而不是一般的實例屬性。如果loadingMessage是輸入屬性並且值已更改,則將在組件上執行更改檢測。 (請參閱@ Vlado對Plunker的回答。)

應該如何輕質內部組件狀態(即,loadingMessage)使用ChangeDetectionStrategy.OnPush時執行?如果有多種最佳實踐,請指出正確的方向。

這是我目前所知:

  • 如果我們改變內部狀態的處理可觀察射擊的事件,或者一部分一部分地改變檢測執行OnPush組件(即,視圖將更新)。在你的具體情況下,我對這個觀點沒有更新感到驚訝。這裏有兩個猜測爲什麼不是:
    • 添加delay()使角度看它更像一個setTimeout()而不是一個可觀察的變化。 setTimeout s不會導致OnPush組件上的更改檢測執行。在您的示例中,更改檢測器在loadingMessage的值更改前2秒完成了其工作。
    • 就像@Sasxa在他的回答中所示,您必須在模板中對Observable進行綁定。也就是說,它可能不僅僅是「可觀察到的火災」......也許它必須是可觀察到的火災發生的界限。在這種情況下,創建loadingMessage(或甚至更通用的state屬性)作爲Observable本身將允許您將模板綁定到其值(或多個異步值),請參閱this example plnkr
      更新2016-04-28:它看起來綁定必須包括| async,如plnkr所示,並在this plnkr
  • 如果我們改變內部狀態,它不是事件處理或可觀察到的點火部分,我們可以注入ChangeDetectorRef到我們的組件並調用方法markForCheck()引起變化檢測到OnPush組件和所有祖先組件上執行直到根組件。如果只改變視圖狀態(即,對於組件本地的狀態並且可能是其後代),則可以使用detectChanges()來替代,其不會標記用於改變檢測的所有祖先組件。 Plunker
+0

感謝您的全面解答。我在想,最好的解決方案是'markForCheck()'(我對這是否有點冒險或可以接受是有界限的)。或者創建一個'loadingMessage'或'state'作爲一個'Observable'本身,這樣它可以代表多個狀態,按照[plnkr](http://plnkr.co/edit/OiBSDA8Kcsgl4vBF15xW?p=preview)。我想我會將其添加到您的答案中並接受,因爲它似乎涵蓋了所有基數。 –

+2

@PhilipBulley,'markForCheck()'不是很好。 [ChangeDetectorRef API文檔](https://angular.io/docs/ts/latest/api/core/ChangeDetectorRef-class.html)顯示了一個與您的用例非常相似的示例:具有「OnPush」的組件具有「在setTimeout()'回調中更新的numberOfTicks屬性,調用'markForCheck()'以確保視圖更新。 –

+0

感謝您使用示例進行說明。 –

4

據我所知,OnPush正在directly with observables時使用:

//our root app component 
import {Component, OnInit, ChangeDetectionStrategy} from 'angular2/core' 
import {Observable} from 'rxjs/Observable'; 
import 'rxjs/Rx'; 

@Component({ 
    selector: 'my-app', 
    template: `Are we loading?: {{ loadingMessage |async }}`, 
    changeDetection: ChangeDetectionStrategy.OnPush 
}) 
export class App implements OnInit { 
    private loadingMessage; 
    constructor() { } 

    ngOnInit() { 
    this.loadingMessage = Observable.of(true) 
     .delay(2000) 
    } 
} 
+0

這是一個很好的解決方案,基於我簡化的例子。實際上,返回'Observable'的調用不是在'ngOnInit'中完成的,而是在一個表單提交處理程序中完成的。這意味着模板綁定將需要'{{loadingMessage && loadingMessage |異步}}。 –

+0

...另外,在現實中,我使用單個屬性'state'維護內部狀態,加載只是幾個狀態之一。我猜'state'可能是另一個內部的'Observable',按照[plnkr](http://plnkr.co/edit/OiBSDA8Kcsgl4vBF15xW?p=preview)。隨着時間的推移,可能會推出多個「狀態」值。 –

+2

@PhilipBulley另請注意,Angular2表單具有內置的可觀察對象('valueChanges'屬性)。你可能也想看看這個...... – Sasxa

1

有了這個ChangeDetectionStrategy.OnPush你告訴你的組件只監聽更改它的輸入特性。

我添加加載組件到你的例子只是向你展示它是如何工作的: http://plnkr.co/edit/k5tkmcLlzkwz4t5ifqFD?p=preview

+0

感謝你的演示,Vlado。創建一個嵌套組件來檢測變化並不是一個很好的解決方案,但它確實很好地說明了你的觀點。 –

+0

實際上,我不知道爲什麼'loadingMessage'沒有呈現給'test',因爲我們在'setTimeout'的最後一個值中設置了'test'的值。有人可以幫我解釋一下嗎? –