2016-02-14 12 views
27

Próbuję otoczyć głowę najlepszą praktyką podczas korzystania z Observables obok ChangeDetectionStrategy.OnPush.ChangeDetectionStrategy.OnPush and Observable.subscribe w Angular 2

Przykład pokazuje wspólny scenariusz chcąc pokazać jakieś wiadomości ładunkowej (lub prostą animację pokrętła być może):

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!'; 
     } 

     }); 

    } 
} 

I mniej lub bardziej zrozumieć wymóg niezmienności z OnPush i, przynajmniej dla mnie, ma to obecnie sens, gdy mówimy o rzeczywistych danych modelu (prawdopodobnie w jakimś sklepie).

Tak, mam dwa pytania:

  1. Dlaczego nie przypisanie nowej wartości ciągu 'Success!' wyzwalają detektor zmiany? Jeśli chodzi o niezmienność, wartość się zmieniła, prawda?
  2. W jaki sposób należy wdrożyć lekki składnik wewnętrzny składnika (np. loadingMessage) podczas korzystania z ChangeDetectionStrategy.OnPush? Jeśli istnieje wiele dobrych praktyk, wskaż mi właściwy kierunek.

Odpowiedz

39

dobre pytanie. Mam dwa cytaty z Savkin o onPush (ponieważ Angular.io docs wydają się nie mieć żadnych informacji na ten temat jeszcze):

Ramy sprawdzi OnPush komponenty tylko wtedy, gdy zmieniają ich wejścia lub szablony komponenty emitować zdarzenia. - ref

Podczas korzystania z OnPush, Angular będzie sprawdzać komponent tylko wtedy, gdy zmieni się dowolna z jego właściwości wejściowych, kiedy zostanie wywołane zdarzenie lub gdy obserwowalne wystrzeli zdarzenie. - ref (w komentarzu na @vivainio)

Drugi cytat wydaje się bardziej kompletny. (Szkoda, że ​​został pochowany w komentarzu!)

Dlaczego nie przypisanie nowej wartości ciągu Success! wywoła detektor zmiana? Jeśli chodzi o niezmienność, wartość się zmieniła, prawda?

OnPush niezmienność odnosi się do właściwości wejściowych, a nie do normalnych właściwości instancji. Jeśli byłaby właściwością wejściową, a wartość zmieni się, to wykrywanie zmiany zostanie wykonane na komponencie. (Patrz odpowiedź @ Vlado dla Plunkera.)

Jak lekki stan komponent wewnętrzny (tj loadingMessage) być realizowane przy użyciu ChangeDetectionStrategy.OnPush? Jeśli istnieje wiele dobrych praktyk, wskaż mi właściwy kierunek.

Oto, co wiem do tej pory:

  • W przypadku zmiany stanu wewnętrznego jako część obsługi zdarzenia lub część obserwowanej wypalania, wykrywanie zmiany wykonuje na komponencie OnPush (tj widok zostanie zaktualizowany). W twoim konkretnym przypadku byłem zaskoczony, że widok nie został zaktualizowany. Oto dwa przypuszczenia, dlaczego nie:
    • Dodanie delay() powoduje, że kątowy wygląda bardziej jak setTimeout() niż zauważalna zmiana. setTimeout s nie powoduje wykonania wykrywania zmian w komponencie OnPush. W twoim przykładzie Detektor zmian zakończył swoją pracę na 2 sekundy przed zmianą wartości loadingMessage.
    • Podobnie jak @Sasxa pokazuje w swojej odpowiedzi, musisz mieć powiązanie w szablonie z Observable. Oznacza to, że może to być coś więcej niż "obserwowalne pożary" ... może musi to być obserwowalne, które wystrzeliwuje. W takim przypadku utworzenie loadingMessage (lub nawet bardziej ogólnej właściwości state) jako samej Observable umożliwi powiązanie szablonu z jego wartością (lub wieloma wartościami asynchronicznymi), patrz this example plnkr.
      Aktualizacja 2016-04-28: wygląda na to, że wiązanie musi zawierać | async, jak pokazano na stronie plnkr, oraz w this plnkr.
  • Jeśli mamy zmienić stan wewnętrzny i nie jest częścią obsługi zdarzeń lub obserwowalnych wypalania, możemy wstrzyknąć ChangeDetectorRef naszego komponentu i wywołać metodę markForCheck() spowodować wykrycie zmian do wykonania na komponencie OnPush i wszystkich komponentów przodków aż do komponentu głównego. Jeśli zmieniany jest tylko stan widoku (tj. Stan lokalny dla komponentu i być może jego potomkowie), można zamiast tego użyć parametru detectChanges(), który nie zaznaczy wszystkich składników przodka w celu wykrycia zmiany. Plunker
+0

Dziękujemy za wyczerpującą odpowiedź. Myślę, że najlepszym rozwiązaniem jest albo 'markForCheck()' (jestem graniczny, czy jest to trochę hacky, czy akceptowalne). Lub tworzenie wartości 'loadingMessage' lub' state' jako wartości 'Observable', dzięki czemu może reprezentować wiele stanów zgodnie z tym [plnkr] (http://plnkr.co/edit/OiBSDA8Kcsgl4vBF15xW?p=preview).Myślę, że dodam to do twojej odpowiedzi i zaakceptuję, ponieważ wydaje się, że obejmuje wszystkie podstawy. –

+2

@PhilipBulley, 'markForCheck()' nie jest zhakowany. Dokument [ChangeDetectorRef API] (https://angular.io/docs/ts/latest/api/core/ChangeDetectorRef-class.html) pokazuje przykład bardzo podobny do twojego przypadku użycia: komponent z 'OnPush' ma' Właściwość numberOfTicks, która jest aktualizowana w wywołaniu 'setTimeout()', a funkcja 'markForCheck()' jest wywoływana w celu zapewnienia aktualizacji widoku. –

+0

Dzięki za wyjaśnienie na przykładzie. –

4

AFAIK, OnPush jest używany podczas pracy 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

To jest dobre rozwiązanie oparte na moim uproszczonym przykładzie. W rzeczywistości wywołanie, które zwraca wartość "Observable", nie jest wykonywane w 'ngOnInit', ale zamiast tego w formularzu przesyłamy program obsługi. Oznacza to, że powiązanie szablonu musiałoby mieć postać '{{loadingMessage && loadingMessage | async}} '. –

+0

... Również w rzeczywistości utrzymuję stan wewnętrzny za pomocą pojedynczej właściwości "stan", ładowanie jest tylko jednym z kilku stanów. Myślę, że "stan" może być kolejnym wewnętrznym 'Observable', jak na tym [plnkr] (http://plnkr.co/edit/OiBSDA8Kcsgl4vBF15xW?p=preview). Wiele wartości 'state' może zostać z czasem do niego przesuniętych. –

+2

@PhilipBulley Należy również zauważyć, że formularze Angular2 mają wbudowane obserwowalne obiekty (własność 'valueChanges'). Może chcesz też to sprawdzić ... – Sasxa

1

Dzięki tej ChangeDetectionStrategy.OnPush mówisz swój składnik słuchać tylko o zmiany na jego właściwości wejściowych.

dodałem komponent ładowania do przykładu tylko pokazać, jak to działa: http://plnkr.co/edit/k5tkmcLlzkwz4t5ifqFD?p=preview

+0

Dziękuję za twoje demo, Vlado. Tworzenie komponentu zagnieżdżonego w celu wykrycia zmiany nie jest dobrym rozwiązaniem, ale dobrze ilustruje Twój punkt widzenia. –

+0

Właściwie nie wiem, dlaczego 'loadingMessage' nie jest renderowany na' test', ponieważ ustawiamy wartość na 'test' na końcu' setTimeout'. Czy ktoś mógłby mi pomóc to wyjaśnić? –