Subject of Core Concept of Angular 2 Learning-RxJS

Subject

What is Subject? In RxJS, Subject is a special Observable that can push values to multiple Observers. Ordinary Observable does not have the ability to push multiple routes (each Observer has its own independent execution environment), while Subject can share an execution environment.

Subject is an observable object that can be pushed multiple ways. Like EventEmitter, Subject maintains its own Observer.

Each Subject is an Observable. For a Subject, you can subscribe to it. Observer receives data as usual. From the perspective of Observer, it can not distinguish whether its execution environment is one-way push of ordinary Observable or multi-way push based on Subject.

In the internal implementation of Subject, no new execution environment is created after subscribe. It simply registers the new Observer in the list of Observers it maintains, similar to the addListener mechanism in other languages and libraries.

Each Subject can also be an Observer (Observer) Subject, which is also an object composed of next(v), error(e), and complete(). After calling the next (the Value) method, the Subject multiplexes the Value to all Observers already registered on it.

In the following example, we registered two Observer s on Subject and multiplexed some values:

var subject = new Rx.Subject();

subject.subscribe({
  next: (v) => console.log('observerA: ' + v)
});
subject.subscribe({
  next: (v) => console.log('observerB: ' + v)
});

subject.next(1);
subject.next(2);

The output of the console is as follows:

observerA: 1
observerB: 1
observerA: 2
observerB: 2

Since Subject is an Observer, you can use it as a parameter for subscribe (subscribe) normal Observable, as shown in the following example:

var subject = new Rx.Subject();

subject.subscribe({
  next: (v) => console.log('observerA: ' + v)
});
subject.subscribe({
  next: (v) => console.log('observerB: ' + v)
});

var observable = Rx.Observable.from([1, 2, 3]);

observable.subscribe(subject); // You can pass it on. Subject Subscribe to observable

The results after implementation are as follows:

observerA: 1
observerB: 1
observerA: 2
observerB: 2
observerA: 3
observerB: 3

Through the above implementation: We find that we can convert ordinary Observable single push to multi-push through Subject. This illustrates the role of Subject as a bridge between single Observable and multiple Observable.

There are also several special types of Subjects, BehaviorSubject, ReplaySubject, and AsyncSubject.

Observable for Multiplex Push

In future contexts, whenever we refer to "Observable for multi-push", we refer specifically to the Observable execution environment built through Subject. Otherwise, "plain Observable" is just a series of values that will not share the execution environment and will not take effect until they are subscribed to.

By using Subject, you can create multiple Observable s with the same execution environment.

Here's how multipath works: Subject subscribes to data from a plain Observable, and then other Observer s subscribe to the Subject, as shown below:

var source = Rx.Observable.from([1, 2, 3]);
var subject = new Rx.Subject();
var multicasted = source.multicast(subject);

// adopt`subject.subscribe({...})`Subscribe Subject Of Observer: 
multicasted.subscribe({
  next: (v) => console.log('observerA: ' + v)
});
multicasted.subscribe({
  next: (v) => console.log('observerB: ' + v)
});

// Give Way Subject Effective from data source subscriptions:
multicasted.connect();

The multicast method returns an observable object similar to Observable, but after it has been subscribed, it represents the characteristics of Subject.
The object returned by multicast is also of Connectable Observable type and has a connect() method.
The connect() method is very important, and it determines when Observable starts executing. Since Observable starts executing after calling connect(), connect() returns a Subscription for the caller to terminate execution.

Reference counting

The execution of Subscription control returned by manually calling connect() is very complicated. Usually, we want to automatically connnect after the first Observer subscribes to a Subject, and terminate the Subject when all Observers cancel the subscription.

Let's analyze the subscription process in the following example:

  1. The first Observer subscribed to Observable for multi-push
  2. Multiple Observable Connected
  3. Send a next notification with a value of 0 to the first Observer
  4. The second Observer subscribes to Observable for multi-push
  5. Send next notification with a value of 1 to the first Observer
  6. Send next notification with a value of 1 to the second Observer
  7. The first Observer cancelled the subscription to Observable for multi-push
  8. Send a next notification with a value of 2 to the second Observer
  9. The second Observer cancels the subscription to Observable for multi-push
  10. Cancel the connection to Observable for multi-push

By explicitly calling connect(), the code is as follows:

var source = Rx.Observable.interval(500);
var subject = new Rx.Subject();
var multicasted = source.multicast(subject);
var subscription1, subscription2, subscriptionConnect;

subscription1 = multicasted.subscribe({
  next: (v) => console.log('observerA: ' + v)
});
subscriptionConnect = multicasted.connect();

setTimeout(() => {
  subscription2 = multicasted.subscribe({
    next: (v) => console.log('observerB: ' + v)
  });
}, 600);

setTimeout(() => {
  subscription1.unsubscribe();
}, 1200);

setTimeout(() => {
  subscription2.unsubscribe();
  subscriptionConnect.unsubscribe(); 
}, 2000);

If you don't want to explicitly call the connect() method, you can call the refCount() method on the Observable of the ConnectableObservable type. The method counts references: records the behavior of Observable being subscribed to. refCount() calls the connect() method when subscriptions range from 0 to 1. By subscribing from 1 to 0, he will terminate the entire execution process.
refCount() causes the multi-push Observable to execute automatically after being subscribed, and stops executing after all observers cancel the subscription.

The following is an example:

var source = Rx.Observable.interval(500);
var subject = new Rx.Subject();
var refCounted = source.multicast(subject).refCount();
var subscription1, subscription2, subscriptionConnect;

console.log('observerA subscribed');
subscription1 = refCounted.subscribe({
  next: (v) => console.log('observerA: ' + v)
});

setTimeout(() => {
  console.log('observerB subscribed');
  subscription2 = refCounted.subscribe({
    next: (v) => console.log('observerB: ' + v)
  });
}, 600);

setTimeout(() => {
  console.log('observerA unsubscribed');
  subscription1.unsubscribe();
}, 1200);

setTimeout(() => {
  console.log('observerB unsubscribed');
  subscription2.unsubscribe();
}, 2000);

The output is as follows:

observerA subscribed
observerA: 0
observerB subscribed
observerA: 1
observerB: 1
observerA unsubscribed
observerB: 2
observerB unsubscribed

Only ConnectableObservables have refCount() method, which returns an Observable instead of a new ConnectableObservable.

BehaviorSubject

BehaviorSubject is a derivative of Subject, which has the concept of "the latest value". It always saves the value recently sent to the data consumer, and when an Observer subscribes, it immediately receives "the latest value" from BehaviorSubject.

BehaviorSubjects are well suited to represent "values over time". For example, Subject denotes a person's birthday, while Behavior denotes a person's age. (Birthday is only one day, and a person's age will remain until the next birthday.)

The following example shows how to initialize BehaviorSubject with 0, which is the first pushed value when Observer subscribes to it. Next, before the second Observer subscribes to Behavior Subject, it pushes 2. Although the subscription is after push 2, the second Observer still accepts 2:

var subject = new Rx.BehaviorSubject(0 /* initial value */);

subject.subscribe({
  next: (v) => console.log('observerA: ' + v)
});

subject.next(1);
subject.next(2);

subject.subscribe({
  next: (v) => console.log('observerB: ' + v)
});

subject.next(3);

The output results are as follows:

observerA: 0
observerA: 1
observerA: 2
observerB: 2
observerA: 3
observerB: 3

ReplaySubject

ReplaySubject is like BehaviorSubject, which is a subclass of Subject. Old values can be pushed to new subscribers through ReplaySubject, just as a video recorder ReplaySubject can record part of Observable's status (values pushed in the past time).

A ReplaySubject can record multiple values pushed during Observable execution and playback them to new subscribers.
You can specify the number of playback values:

var subject = new Rx.ReplaySubject(3 /* Playback quantity */);

subject.subscribe({
  next: (v) => console.log('observerA: ' + v)
});

subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);

subject.subscribe({
  next: (v) => console.log('observerB: ' + v)
});

subject.next(5);

The output is as follows:

observerA: 1
observerA: 2
observerA: 3
observerA: 4
observerB: 2
observerB: 3
observerB: 4
observerA: 5
observerB: 5

In addition to the number of playbacks, you can also specify "window time" in milliseconds to determine how long ago the ReplaySubject recorded the value of the Observable push. In the following example, we set the number of playback to 100 and the window time to 500 milliseconds:

var subject = new Rx.ReplaySubject(100, 500 /* windowTime */);

subject.subscribe({
  next: (v) => console.log('observerA: ' + v)
});

var i = 1;
setInterval(() => subject.next(i++), 200);

setTimeout(() => {
  subject.subscribe({
    next: (v) => console.log('observerB: ' + v)
  });
}, 1000);

The second Observer accepts 3 (600ms), 4 (800ms) and 5 (1000ms), all of which are pushed within 500 milliseconds before subscription (window length 1000ms - 600ms = 400ms < 500ms):

observerA: 1
observerA: 2
observerA: 3
observerA: 4
observerA: 5
observerB: 3
observerB: 4
observerB: 5
observerA: 6
observerB: 6
...

AsyncSubject

AsyncSubject is another derivative of Subject, and Observable only pushes the last value in the execution environment after execution is complete.

var subject = new Rx.AsyncSubject();

subject.subscribe({
  next: (v) => console.log('observerA: ' + v)
});

subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);

subject.subscribe({
  next: (v) => console.log('observerB: ' + v)
});

subject.next(5);
subject.complete();

The output results are as follows:

observerA: 5
observerB: 5

AsyncSubject is similar to the last() operator in that it pushes the last value of the execution process while waiting for the notification to be completed.

Come from: https://segmentfault.com/a/1190000005069851

Posted by dsainteclaire on Sat, 20 Apr 2019 23:27:34 -0700