BehaviorSubject vs. Observable?


690

Patrzę na wzorce Angular RxJs i nie rozumiem różnicy między a BehaviorSubjecta an Observable.

Z mojego zrozumienia, a BehaviorSubjectjest wartością, która może się zmieniać w czasie (może być subskrybowana, a subskrybenci mogą otrzymywać zaktualizowane wyniki). To wydaje się być dokładnie tym samym celem Observable.

Kiedy użyjesz Observablevs a BehaviorSubject? Czy są zalety korzystania z opcji „ BehaviorSubjectover an Observableor vice versa”?

Odpowiedzi:


969

BehaviourSubject jest rodzajem tematu, temat jest specjalnym typem obserwowalnego, więc możesz subskrybować wiadomości jak każdy inny obserwowalny. Unikalne cechy BehaviorSubject to:

  • Potrzebuje wartości początkowej, ponieważ zawsze musi zwrócić wartość przy subskrypcji, nawet jeśli nie otrzymała next()
  • Po subskrypcji zwraca ostatnią wartość przedmiotu. Regularne obserwowalne uruchamia się tylko wtedy, gdy otrzymaonnext
  • w dowolnym momencie możesz pobrać ostatnią wartość przedmiotu w nieobserwowalnym kodzie za pomocą tej getValue()metody.

Unikalne cechy przedmiotu w porównaniu do obserwowalnego to:

  • Jest obserwatorem, a ponadto jest obserwowalny, dzięki czemu można także wysyłać wartości do przedmiotu oprócz subskrybowania go.

Ponadto można uzyskać obserwowalny obiekt z zachowaniem, stosując asObservable()metodę na BehaviorSubject.

Obserwowalny jest ogólny i BehaviorSubjecttechnicznie jest podtypem obserwowalnym, ponieważ obiekt behawioralny jest obserwowalny z określonymi cechami.

Przykład z BehaviorSubject :

// Behavior Subject

// a is an initial value. if there is a subscription 
// after this, it would get "a" value immediately
let bSubject = new BehaviorSubject("a"); 

bSubject.next("b");

bSubject.subscribe(value => {
  console.log("Subscription got", value); // Subscription got b, 
                                          // ^ This would not happen 
                                          // for a generic observable 
                                          // or generic subject by default
});

bSubject.next("c"); // Subscription got c
bSubject.next("d"); // Subscription got d

Przykład 2 z regularnym przedmiotem:

// Regular Subject

let subject = new Subject(); 

subject.next("b");

subject.subscribe(value => {
  console.log("Subscription got", value); // Subscription wont get 
                                          // anything at this point
});

subject.next("c"); // Subscription got c
subject.next("d"); // Subscription got d

Obserwowalne można utworzyć zarówno z nich, jak Subjecti BehaviorSubjectprzy użyciu subject.asObservable().

Jedyna różnica polega na tym, że nie można wysłać wartości do obserwowalnej za pomocą next()metody.

W usługach Angular użyłbym usługi BehaviorSubjectdanych, ponieważ usługa kątowa często inicjuje się, zanim komponent i zachowanie zapewni, że komponent korzystający z usługi odbierze ostatnie zaktualizowane dane, nawet jeśli nie będzie żadnych nowych aktualizacji od czasu subskrypcji komponentu na te dane.


7
Trochę mylę się z przykładem 2 zwykłego przedmiotu. Dlaczego subskrypcja nie dostanie niczego, nawet w drugim wierszu, w którym wysyłasz wartości do podmiotu za pomocą podmiotu.następna („b”)?
jmod999

25
@ jmod999 Drugi przykład to zwykły podmiot, który otrzymuje wartość tuż przed wywołaniem subskrypcji. W przypadku przedmiotów regularnych subskrypcja jest uruchamiana tylko dla wartości otrzymanych po wywołaniu subskrypcji. Ponieważ a jest odbierane bezpośrednio przed subskrypcją, nie jest wysyłane do subskrypcji.
Shantanu Bhadoria

Uwaga na temat tego fantastycznego rozwiązania: jeśli użyjesz go w funkcji i zwrócisz, zwróć obserwowalne. Miałem pewne problemy ze zwrotem tematu, co dezorientuje innych programistów, którzy wiedzą tylko, czym są Observables
sam

8
W środę miałem wywiad z Angular 4. Ponieważ wciąż uczę się nowej platformy, potknął mnie, pytając: „Co się stanie, jeśli zasubskrybuję obserwowalny, który jest w module, który nie został jeszcze załadowany?” Nie byłem pewien, ale powiedział mi, że odpowiedzią było użycie BSubject - DOKŁADNIE, jak Pan Bhadoria wyjaśnił to powyżej. Odpowiedzią było użycie BSubject, ponieważ zawsze zwraca ostatnią wartość (przynajmniej tak pamiętam ostatni komentarz ankietera na ten temat).
bob.mazzo

1
@ bob.mazzo Dlaczego w tej sprawie muszę użyć obiektu BSubject? - Jeśli zasubskrybuję tego obserwatora, nic nie otrzymam, ponieważ obserwator nie został zainicjowany, więc nie może przekazywać danych do obserwatorów, a jeśli użyję obiektu BSubject, nie otrzymam niczego z tego samego powodu. W obu przypadkach subskrybent nic nie otrzyma, ponieważ znajduje się w module, który nie został zainicjowany. Czy mam rację?
Rafael Reyes,

183

Obserwowalny: inny wynik dla każdego obserwatora

Jedna bardzo ważna różnica. Ponieważ Observable jest tylko funkcją, nie ma żadnego stanu, więc dla każdego nowego Observera wykonuje powtarzalny kod tworzenia. To skutkuje:

Kod jest uruchamiany dla każdego obserwatora. Jeśli jest to połączenie HTTP, jest wywoływane dla każdego obserwatora

To powoduje poważne błędy i nieefektywności

BehaviorSubject (lub Subject) przechowuje dane obserwatora, uruchamia kod tylko raz i daje wynik wszystkim obserwatorom.

Dawny:

JSBin: http://jsbin.com/qowulet/edit?js,console

// --- Observable ---
let randomNumGenerator1 = Rx.Observable.create(observer => {
   observer.next(Math.random());
});

let observer1 = randomNumGenerator1
      .subscribe(num => console.log('observer 1: '+ num));

let observer2 = randomNumGenerator1
      .subscribe(num => console.log('observer 2: '+ num));


// ------ BehaviorSubject/ Subject

let randomNumGenerator2 = new Rx.BehaviorSubject(0);
randomNumGenerator2.next(Math.random());

let observer1Subject = randomNumGenerator2
      .subscribe(num=> console.log('observer subject 1: '+ num));
      
let observer2Subject = randomNumGenerator2
      .subscribe(num=> console.log('observer subject 2: '+ num));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.3/Rx.min.js"></script>

Wynik :

"observer 1: 0.7184075243594013"
"observer 2: 0.41271850211336103"
"observer subject 1: 0.8034263165479893"
"observer subject 2: 0.8034263165479893"

Obserwuj, jak przy użyciu Observable.createutworzono różne dane wyjściowe dla każdego obserwatora, ale BehaviorSubjectdało to samo wyjście dla wszystkich obserwatorów. To jest ważne.


Inne różnice podsumowane.

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃         Observable                  ┃     BehaviorSubject/Subject         ┃      
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ 
┃ Is just a function, no state        ┃ Has state. Stores data in memory    ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ Code run for each observer          ┃ Same code run                       ┃
┃                                     ┃ only once for all observers         ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ Creates only Observable             ┃Can create and also listen Observable┃
┃ ( data producer alone )             ┃ ( data producer and consumer )      ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ Usage: Simple Observable with only  ┃ Usage:                              ┃
┃ one Obeserver.                      ┃ * Store data and modify frequently  ┃
┃                                     ┃ * Multiple observers listen to data ┃
┃                                     ┃ * Proxy between Observable  and     ┃
┃                                     ┃   Observer                          ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

3
każdy, kto przyjedzie KnockoutJS's ko.observable(), natychmiast zobaczy więcej podobieństw Rx.BehaviorSubjectdoRx.Observable
Simon_Weaver

@Skeptor Observable: metoda subscribe zawsze wyzwala metodę onNext powiązaną z obserwatorem i zwraca wartość zwracaną. BehaviourSubject / Subject: Zawsze zwróci najnowszą wartość w strumieniu. tutaj metoda subskrybowania z tematem nie uruchomi metody onNext jego obserwatora, dopóki nie znajdzie najnowszej wartości w strumieniu.
Mohan Ram

62

Obserwowalne i podmiotowe są obserwowalne, co oznacza, że ​​obserwator może je śledzić. ale oba mają pewne unikalne cechy. Ponadto istnieją w sumie 3 rodzaje przedmiotów, z których każdy ponownie ma unikalne cechy. spróbujmy zrozumieć każdy z nich.

praktyczny przykład znajdziesz tutaj na stackblitz . (Musisz sprawdzić konsolę, aby zobaczyć rzeczywiste wyjście)

wprowadź opis zdjęcia tutaj

Observables

Są zimni: kod jest wykonywany, gdy ma przynajmniej jednego obserwatora.

Tworzy kopię danych: Observable tworzy kopię danych dla każdego obserwatora.

Jednokierunkowy: Obserwator nie może przypisać wartości do obserwowalnej (origin / master).

Subject

Są gorące: kod jest wykonywany, a wartość jest rozgłaszana, nawet jeśli nie ma obserwatora.

Udostępnia dane: Te same dane są udostępniane wszystkim obserwatorom.

dwukierunkowy: Obserwator może przypisać wartość do obserwowalnej (origin / master).

Jeśli używasz tematu, tracisz wszystkie wartości, które są nadawane przed utworzeniem obserwatora. A więc nadchodzi powtórka

ReplaySubject

Są gorące: kod jest wykonywany, a wartość przekazywana, nawet jeśli nie ma obserwatora.

Udostępnia dane: Te same dane są udostępniane wszystkim obserwatorom.

dwukierunkowy: Obserwator może przypisać wartość do obserwowalnej (origin / master). plus

Odtwórz strumień wiadomości: bez względu na to, czy subskrybujesz temat powtórki, otrzymasz wszystkie nadawane wiadomości.

W temacie i powtórzeniu nie można ustawić wartości początkowej na możliwą do zaobserwowania. A więc nadchodzi Obiekt Behawioralny

BehaviorSubject

Są gorące: kod jest wykonywany, a wartość przekazywana, nawet jeśli nie ma obserwatora.

Udostępnia dane: Te same dane są udostępniane wszystkim obserwatorom.

dwukierunkowy: Obserwator może przypisać wartość do obserwowalnej (origin / master). plus

Odtwórz strumień wiadomości: bez względu na to, czy subskrybujesz temat powtórki, otrzymasz wszystkie nadawane wiadomości.

Możesz ustawić wartość początkową: Możesz zainicjalizować obserwowalną wartość domyślną.


3
Warto wspomnieć, że a ReplaySubjectma historię i może nadawać / emitować sekwencję (starych) wartości. Tylko gdy bufor jest ustawiony na 1, zachowuje się podobnie jak a BehaviorSubject.
Wilt

28

Obserwowalny obiekt reprezentuje kolekcję opartą na wypychaniu.

Interfejsy obserwatora i obserwowalnego zapewniają uogólniony mechanizm powiadomień opartych na wypychaniu, znany również jako wzorzec projektu obserwatora. Obserwowalny obiekt reprezentuje obiekt, który wysyła powiadomienia (dostawca); obiekt Observer reprezentuje klasę, która je odbiera (obserwator).

Klasa Subject dziedziczy zarówno Obserwowalny, jak i Obserwator, w tym sensie, że jest zarówno obserwatorem, jak i obserwowalnym. Możesz użyć podmiotu, aby zasubskrybować wszystkich obserwatorów, a następnie zasubskrybować ten temat do źródła danych zaplecza

var subject = new Rx.Subject();

var subscription = subject.subscribe(
    function (x) { console.log('onNext: ' + x); },
    function (e) { console.log('onError: ' + e.message); },
    function () { console.log('onCompleted'); });

subject.onNext(1);
// => onNext: 1

subject.onNext(2);
// => onNext: 2

subject.onCompleted();
// => onCompleted

subscription.dispose();

Więcej na https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/subjects.md


Jaka jest różnica między subskrypcją.dispose () a subskrypcją.unsubscribe ()?
Choopage - Jek Bao,

4
@choopage bez różnicy. ten drugi sposób jest nowy
Royi Namir

Jeśli anulujesz subskrypcję, zanim temat zostanie usunięty, w przeciwnym razie subskrypcja stanie się śmieciem, ponieważ subskrybuje wartość zerową.
Sophie Zhang,

20

Jednej rzeczy, której nie widzę w przykładach, jest to, że kiedy rzutujesz BehaviorSubject na Observable za pośrednictwem asObservable, dziedziczy zachowanie zwracania ostatniej wartości po subskrypcji.

Jest to trudny bit, ponieważ biblioteki często ujawniają pola jako obserwowalne (tj. Parametry w ActivatedRoute w Angular2), ale mogą używać obiektu Subject lub BehaviorSubject za scenami. Ich użycie wpłynęłoby na zachowanie subskrybowania.

Zobacz tutaj http://jsbin.com/ziquxapubo/edit?html,js,console

let A = new Rx.Subject();
let B = new Rx.BehaviorSubject(0);

A.next(1);
B.next(1);

A.asObservable().subscribe(n => console.log('A', n));
B.asObservable().subscribe(n => console.log('B', n));

A.next(2);
B.next(2);

11

Obserwowalne pozwala subskrybować tylko natomiast tematu pozwala zarówno publikowania i subskrybowania.

Zatem przedmiot zezwala na twoje usługi zarówno jako wydawca, jak i subskrybent.

W tej chwili nie jestem zbyt dobry, Observablewięc podzielę się tylko przykłademSubject .

Lepiej zrozummy na przykładzie Angular CLI . Uruchom poniższe polecenia:

npm install -g @angular/cli

ng new angular2-subject

cd angular2-subject

ng serve

Zamień zawartość na app.component.html:

<div *ngIf="message">
  {{message}}
</div>

<app-home>
</app-home>

Uruchom polecenie, ng g c components/homeaby wygenerować komponent główny. Zamień zawartość na home.component.html:

<input type="text" placeholder="Enter message" #message>
<button type="button" (click)="setMessage(message)" >Send message</button>

#messagejest tutaj zmienną lokalną. Dodaj właściwość message: string; doapp.component.ts klasy.

Uruchom to polecenie ng g s service/message. Spowoduje to wygenerowanie usługi na src\app\service\message.service.ts. Zapewnij tę usługę aplikacji .

Importuj Subjectdo MessageService. Dodaj też temat. Ostateczny kod powinien wyglądać następująco:

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class MessageService {

  public message = new Subject<string>();

  setMessage(value: string) {
    this.message.next(value); //it is publishing this value to all the subscribers that have already subscribed to this message
  }
}

Teraz wstaw tę usługę home.component.tsi przekaż jej instancję do konstruktora. Zrób to app.component.tsteż. Użyj tej instancji usługi do przekazania wartości #messagedo funkcji usługi setMessage:

import { Component } from '@angular/core';
import { MessageService } from '../../service/message.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent {

  constructor(public messageService:MessageService) { }

  setMessage(event) {
    console.log(event.value);
    this.messageService.setMessage(event.value);
  }
}

Wewnątrz app.component.tssubskrybuj i anuluj subskrypcję (aby zapobiec wyciekom pamięci) do Subject:

import { Component, OnDestroy } from '@angular/core';
import { MessageService } from './service/message.service';
import { Subscription } from 'rxjs/Subscription';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {

  message: string;
  subscription: Subscription;

  constructor(public messageService: MessageService) { }

  ngOnInit() {
    this.subscription = this.messageService.message.subscribe(
      (message) => {
        this.message = message;
      }
    );
  }

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

Otóż ​​to.

Teraz każda wartość weszła do środka #messagez home.component.htmldrukowane są do {{message}}wewnątrzapp.component.html


Dlaczego gigantyczny obraz? Jeśli nie jest to bezpośrednio związane z twoją odpowiedzią, wygląda na to, że głosujesz na wotum.
ruffin

@ruffin To tylko średnia odpowiedź ze średnią liczbą głosów, spójrz na mój profil. Zdecydowanie nie głosuj: D
Mohammed Zameer

1
Dałem ci wcześniej głos, ale uniknąłeś pytania, dlaczego obraz jest tam. To nie jest bezpośrednio związane z twoją odpowiedzią. Nie ma znaczenia, czy masz dużo powtórzeń, czy nie - jeśli obraz nie jest bezpośrednio i konkretnie wyjaśniający, poprosiłbym o jego usunięcie . / wzruszenie ramionami
ruffin

1
@ruffin Jeśli jest to sprzeczne ze zgodą społeczności, to na pewno nie powinno tam być!
Mohammed Zameer,

4

app.component.ts

behaviourService.setName("behaviour");

behiour.service.ts

private name = new BehaviorSubject("");
getName = this.name.asObservable();`

constructor() {}

setName(data) {
    this.name.next(data);
}

custom.component.ts

behaviourService.subscribe(response=>{
    console.log(response);    //output: behaviour
});

1

BehaviorSubject vs Observable : RxJS ma obserwatorów i obserwowalnych, Rxjs oferuje wiele klas do wykorzystania ze strumieniami danych, a jedną z nich jest BehaviorSubject.

Obserwowalne : Obserwowalne to leniwe zbiory wielu wartości w czasie.

BehaviorSubject : Temat, który wymaga wartości początkowej i wysyła swoją aktualną wartość do nowych subskrybentów.

 // RxJS v6+
import { BehaviorSubject } from 'rxjs';

const subject = new BehaviorSubject(123);

//two new subscribers will get initial value => output: 123, 123
subject.subscribe(console.log);
subject.subscribe(console.log);

//two subscribers will get new value => output: 456, 456
subject.next(456);

//new subscriber will get latest value (456) => output: 456
subject.subscribe(console.log);

//all three subscribers will get new value => output: 789, 789, 789
subject.next(789);

// output: 123, 123, 456, 456, 456, 789, 789, 789

1

Pomyśl o Obserwowalnych jako o rurze z przepływającą wodą, czasem woda płynie, a czasem nie. W niektórych przypadkach może być potrzebna rura, która zawsze ma w sobie wodę, możesz to zrobić, tworząc specjalną rurę, która zawsze zawiera wodę, bez względu na to, jak mała jest, nazwijmy tę specjalną rurę BehaviorSubject , jeśli tak się dzieje jako dostawca wody w Twojej społeczności, możesz spać spokojnie w nocy, wiedząc, że nowo zainstalowana rura po prostu działa.

Z technicznego punktu widzenia: możesz napotkać przypadki użycia, w których obserwowalny powinien zawsze mieć wartość, być może chcesz przechwycić wartość tekstu wejściowego w czasie, możesz następnie utworzyć instancję BehaviorSubject, aby zapewnić takie zachowanie, powiedzmy:


const firstNameChanges = new BehaviorSubject("<empty>");

// pass value changes.
firstNameChanges.next("Jon");
firstNameChanges.next("Arya");

Następnie można użyć „wartości” do próbkowania zmian w czasie.


firstNameChanges.value;

Jest to przydatne, gdy później połączysz obserwowalne, sprawdzając typ strumienia jako BehaviorSubject, możesz następnie upewnić się, że strumień przynajmniej odpala lub sygnalizuje przynajmniej raz .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.