Comportement Sujet vs Observable?

690

J'examine les motifs angulaires RxJs et je ne comprends pas la différence entre a BehaviorSubjectet an Observable.

D'après ma compréhension, a BehaviorSubjectest une valeur qui peut changer au fil du temps (peut être abonné et les abonnés peuvent recevoir des résultats mis à jour). Cela semble être exactement le même but d'un Observable.

Quand utiliseriez-vous un Observablevs un BehaviorSubject? Y a-t-il des avantages à utiliser un BehaviorSubjectsur un Observableou vice versa?

Kevin Mark
la source

Réponses:

969

BehaviorSubject est un type de sujet, un sujet est un type spécial d'observable afin que vous puissiez vous abonner à des messages comme tout autre observable. Les caractéristiques uniques de BehaviorSubject sont:

  • Il a besoin d'une valeur initiale car il doit toujours retourner une valeur sur abonnement même s'il n'a pas reçu de next()
  • Lors de la souscription, il renvoie la dernière valeur du sujet. Un observable régulier ne se déclenche que lorsqu'il reçoit unonnext
  • à tout moment, vous pouvez récupérer la dernière valeur du sujet dans un code non observable à l'aide de la getValue()méthode.

Les caractéristiques uniques d'un sujet par rapport à un observable sont:

  • C'est un observateur en plus d'être observable, vous pouvez donc envoyer des valeurs à un sujet en plus de vous y abonner.

De plus, vous pouvez obtenir un sujet observable du comportement en utilisant la asObservable()méthode on BehaviorSubject.

Observable est un générique, et BehaviorSubjectest techniquement un sous-type d'Observable parce que BehaviorSubject est un observable avec des qualités spécifiques.

Exemple avec 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

Exemple 2 avec sujet régulier:

// 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

Un observable peut être créé à partir des deux Subjectet en BehaviorSubjectutilisant subject.asObservable().

La seule différence est que vous ne pouvez pas envoyer de valeurs à une next()méthode utilisant observable .

Dans les services angulaires, j'utiliserais BehaviorSubjectpour un service de données car un service angulaire s'initialise souvent avant que le composant et le comportement n'assurent que le composant consommant le service reçoive les dernières données mises à jour même s'il n'y a pas de nouvelles mises à jour depuis l'abonnement du composant à ces données.

Shantanu Bhadoria
la source
7
Je suis un peu confus avec l'exemple 2 de sujet régulier. Pourquoi l'abonnement n'obtiendra rien, même sur la deuxième ligne où vous envoyez des valeurs à subject en utilisant subject.next ("b")?
jmod999
25
@ jmod999 Le deuxième exemple est un sujet régulier qui reçoit une valeur juste avant l'appel à l'abonnement. Dans les sujets réguliers, l'abonnement n'est déclenché que pour les valeurs reçues après l'appel de l'abonnement. Étant donné que a est reçu juste avant l'abonnement, il n'est pas envoyé à l'abonnement.
Shantanu Bhadoria
Une note sur cette solution fantastique, si vous l'utilisez dans une fonction et la renvoyez, retournez un observable. J'ai eu quelques problèmes avec le retour d'un sujet, et cela déroute les autres développeurs qui ne connaissent que les Observables
sam
8
J'ai eu un entretien avec Angular 4 mercredi. Comme j'apprends encore la nouvelle plate-forme, il m'a fait trébucher en me demandant quelque chose comme "Que va-t-il se passer si je m'abonne à un observable qui se trouve dans un module qui n'a pas encore été chargé paresseux?" Je n'étais pas sûr, mais il m'a dit que la réponse était d'utiliser un BSubject - EXACTEMENT comment M. Bhadoria l'a expliqué ci-dessus. La réponse a été d'utiliser un BSubject car il renvoie toujours la dernière valeur (du moins c'est ainsi que je me souviens du dernier commentaire de l'intervieweur à ce sujet).
bob.mazzo
1
@ bob.mazzo Pourquoi dois-je utiliser un BSubject pour ce cas? - Si je m'abonne à cet observateur, je ne recevrai rien parce que l'observateur n'a pas été initialisé, il ne peut donc pas envoyer de données aux observateurs et si j'utilise un BSubject, je ne recevrai rien non plus pour la même raison. Dans les deux cas, l'abonné ne recevra rien car se trouve dans un module qui n'a pas été initialisé. Ai-je raison?
Rafael Reyes
183

Observable: résultat différent pour chaque observateur

Une différence très très importante. Comme Observable est juste une fonction, il n'a pas d'état, donc pour chaque nouvel Observer, il exécute le code de création observable encore et encore. Il en résulte:

Le code est exécuté pour chaque observateur. Si c'est un appel HTTP, il est appelé pour chaque observateur

Cela provoque des bugs majeurs et des inefficacités

BehaviorSubject (ou Subject) stocke les détails de l'observateur, exécute le code une seule fois et donne le résultat à tous les observateurs.

Ex:

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>

Production :

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

Observez comment l'utilisation a Observable.createcréé une sortie différente pour chaque observateur, mais a BehaviorSubjectdonné la même sortie pour tous les observateurs. C'est important.


Autres différences résumées.

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃         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                          ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
Vamshi
la source
3
toute personne venant KnockoutJS's ko.observable()verra immédiatement plus de parallèles par Rx.BehaviorSubjectrapport àRx.Observable
Simon_Weaver
@Skeptor Observable: la méthode subscribe déclenchera toujours la méthode onNext associée à l'observateur et apportera la valeur de retour. BehaviourSubject / Subject: renvoie toujours la dernière valeur du flux. ici, la méthode de souscription avec le sujet ne déclenchera pas la méthode onNext de son observateur jusqu'à ce qu'il trouve la dernière valeur dans le flux.
Mohan Ram
62

Observable et sujet sont tous deux des moyens observables qu'un observateur peut les suivre. mais les deux ont des caractéristiques uniques. De plus, il y a au total 3 types de sujets, chacun ayant à nouveau des caractéristiques uniques. essayons de comprendre chacun d'eux.

vous pouvez trouver l'exemple pratique ici sur stackblitz . (Vous devez vérifier la console pour voir la sortie réelle)

entrez la description de l'image ici

Observables

Ils sont froids: le code est exécuté quand ils ont au moins un seul observateur.

Crée une copie des données: Observable crée une copie des données pour chaque observateur.

Unidirectionnel: l' observateur ne peut pas attribuer de valeur à observable (origine / maître).

Subject

Ils sont chauds: le code est exécuté et la valeur est diffusée même s'il n'y a pas d'observateur.

Partage des données: les mêmes données sont partagées entre tous les observateurs.

bidirectionnel: l' observateur peut attribuer une valeur à observable (origine / maître).

Si vous utilisez un sujet, vous manquez toutes les valeurs diffusées avant la création de l'observateur. Voici donc Rejouer le sujet

ReplaySubject

Ils sont chauds: le code est exécuté et la valeur est diffusée même s'il n'y a pas d'observateur.

Partage des données: les mêmes données sont partagées entre tous les observateurs.

bidirectionnel: l' observateur peut attribuer une valeur à observable (origine / maître). plus

Relire le flux de messages: peu importe lorsque vous vous abonnez à l'objet de relecture, vous recevrez tous les messages diffusés.

Dans le sujet et le sujet de relecture, vous ne pouvez pas définir la valeur initiale sur observable. Voici donc le sujet comportemental

BehaviorSubject

Ils sont chauds: le code est exécuté et la valeur est diffusée même s'il n'y a pas d'observateur.

Partage des données: les mêmes données sont partagées entre tous les observateurs.

bidirectionnel: l' observateur peut attribuer une valeur à observable (origine / maître). plus

Relire le flux de messages: peu importe lorsque vous vous abonnez à l'objet de relecture, vous recevrez tous les messages diffusés.

Vous pouvez définir la valeur initiale: vous pouvez initialiser l'observable avec la valeur par défaut.

Kedar9444
la source
3
Pourrait être utile de mentionner que a ReplaySubjecta un historique et peut diffuser / émettre une séquence de (anciennes) valeurs. Ce n'est que lorsque le tampon est défini sur 1 qu'il se comporte de la même manière que a BehaviorSubject.
Flétrissement
28

L'objet observable représente une collection basée sur la poussée.

Les interfaces Observer et Observable fournissent un mécanisme généralisé de notification basée sur la poussée, également connu sous le nom de modèle de conception d'observateur. L'objet observable représente l'objet qui envoie des notifications (le fournisseur); l'objet Observer représente la classe qui les reçoit (l'observateur).

La classe Subject hérite à la fois d'Observable et d'Observer, dans le sens où elle est à la fois observateur et observable. Vous pouvez utiliser un sujet pour abonner tous les observateurs, puis abonner le sujet à une source de données principale

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();

Plus d'informations sur https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/subjects.md

Md Ayub Ali Sarker
la source
quelle est la difference entre souscription.dispose () et souscription.unsubscribe ()?
choopage - Jek Bao
4
@choopage aucune différence. ce dernier est la nouvelle façon
Royi Namir
Si vous vous désabonnez avant que le sujet ne soit supprimé, sinon, l'abonnement devient une poubelle car il s'abonne à une valeur nulle.
Sophie Zhang
20

Une chose que je ne vois pas dans les exemples est que lorsque vous convertissez BehaviorSubject en Observable via asObservable, il hérite du comportement de retour de la dernière valeur lors de l'abonnement.

C'est le bit délicat, car souvent les bibliothèques exposent les champs comme observables (c'est-à-dire les paramètres dans ActivatedRoute dans Angular2), mais peuvent utiliser Subject ou BehaviorSubject en arrière-plan. Ce qu'ils utilisent affecterait le comportement de l'abonnement.

Voir ici 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);
Lukasz Marek Sielski
la source
11

Un observable vous permet de vous abonner uniquement alors qu'un sujet vous permet à la fois de publier et de vous abonner.

Un sujet permet donc à vos services d'être utilisés à la fois comme éditeur et comme abonné.

Pour l'instant, je ne suis pas si bon dans Observablece domaine, je ne partagerai donc qu'un exemple de Subject.

Comprenons mieux avec un exemple de CLI angulaire . Exécutez les commandes ci-dessous:

npm install -g @angular/cli

ng new angular2-subject

cd angular2-subject

ng serve

Remplacez le contenu de app.component.htmlpar:

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

<app-home>
</app-home>

Exécutez la commande ng g c components/homepour générer le composant home. Remplacez le contenu de home.component.htmlpar:

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

#messageest la variable locale ici. Ajoutez une propriété message: string; à la app.component.tsclasse de.

Exécutez cette commande ng g s service/message. Cela va générer un service à src\app\service\message.service.ts. Fournir ce service à l'application .

Importer Subjectdans MessageService. Ajoutez également un sujet. Le code final doit ressembler à ceci:

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
  }
}

Maintenant, injectez ce service home.component.tset passez-en une instance au constructeur. Faites cela app.component.tsaussi. Utilisez cette instance de service pour transmettre la valeur de #messageà la fonction de service 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);
  }
}

À l'intérieur app.component.ts, abonnez-vous et désabonnez-vous (pour éviter les fuites de mémoire) à 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();
  }
}

C'est ça.

Maintenant, toute valeur entrée à l'intérieur #messagede home.component.htmldoit être imprimée à l' {{message}}intérieurapp.component.html

xameeramir
la source
Pourquoi l'image géante? Si ce n'est pas directement lié à votre réponse, cela ressemble à un appât de vote.
ruffin
@ruffin Ceci est juste une réponse moyenne avec un nombre moyen de votes, regardez mon profil. Pas définitivement votebait: D
xameeramir
1
Je vous ai donné un vote positif plus tôt, mais vous avez esquivé la question de savoir pourquoi l'image est là. Ce n'est pas directement lié à votre réponse. Peu importe si vous avez beaucoup de représentants ou non - si l'image n'est pas directement et spécifiquement élucidante, je vous demanderais de la supprimer . / haussement d'épaules
ruffin
1
@ruffin Si cela va à l'encontre du consentement de la communauté, alors ça ne devrait pas être là sûrement!
xameeramir
4

app.component.ts

behaviourService.setName("behaviour");

behavior.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
});
Chandru Dev
la source
1

BehaviorSubject vs Observable : RxJS a des observateurs et des observables, Rxjs propose plusieurs classes à utiliser avec les flux de données, et l'une d'entre elles est BehaviorSubject.

Observables : les observables sont des collections paresseuses de plusieurs valeurs au fil du temps.

BehaviorSubject : un sujet qui requiert une valeur initiale et émet sa valeur actuelle aux nouveaux abonnés.

 // 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
Éclat
la source
1

Considérez Observables comme un tuyau avec de l'eau qui coule, parfois l'eau coule et parfois non. Dans certains cas, vous pouvez avoir besoin d'un tuyau qui contient toujours de l'eau, vous pouvez le faire en créant un tuyau spécial qui contient toujours de l'eau, peu importe sa taille, appelons ce tuyau spécial BehaviorSubject , si vous vous trouvez être un fournisseur d'eau dans votre communauté, vous pouvez dormir paisiblement la nuit en sachant que votre nouveau tuyau fonctionne.

En termes techniques: vous pouvez rencontrer des cas d'utilisation où un observable doit toujours avoir de la valeur, peut-être que vous voulez capturer la valeur d'un texte d'entrée au fil du temps, vous pouvez ensuite créer une instance de BehaviorSubject pour garantir ce type de comportement, disons:


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

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

Vous pouvez ensuite utiliser "value" pour échantillonner les changements au fil du temps.


firstNameChanges.value;

Cela est pratique lorsque vous combinez Observables plus tard, en examinant le type de votre flux en tant que BehaviorSubject, vous pouvez ensuite vous assurer que le flux se déclenche au moins ou émet un signal au moins une fois .

Ronnel Reposo
la source