I'm not an expert, but this is how I understand Observables, and their benefits over simple arrays.
Ultimately we want a value to passively update itself in our template whenever it changes, without checking it manually.
Something like this:
<div>{{ observableData$ | async }}</div>
The observableData$
variable is an Observable (indicated by the convention of an ending $
), which sends asynchronous messages, able to be unwrapped in your template with the async
pipe.
Your template can consume this just as easily as a simple array.
eg. <div>{{ [1, 2, 3] }}</div>
But the Observable is just a stream of data.
Think of a river, it has a directional stream / flow of water molecules. But the stream is more of a verb than a noun.
What every river needs is a source. Where are the water molecules actually originating from?
In rxjs
, the "source" of an Observable stream is called a Subject
.
The Observable doesn't "do" anything but provide a conduit from your "source" / Subject
to wherever you're subscribing
to the data. Just like a garden hose doesn't "do" anything but direct the water flowing through it.
Now, think of a BehaviorSubject
as simply a Subject
with a specified initial value.
What real benefit do I get from using BehaviouralSubject? Using arrays is way easier..
To use a BehaviorSubject
is simply to include one piece of a few pieces needed for an Observable stream.
Observable streams are asynchronous and update themselves, like a radio station, to whoever is "tuned in" or subscribed
.
A simple array obviously has a simpler syntax for storing and extracting data, but an array can't update itself across multiple components without manual intervention. This is the value of an Observable stream.
Additionally, the BehaviorSubject
is kept private because nothing external should need direct access to it in the first place. An external component would only need to access an Observable stream, not the BehaviorSubject
itself. The source is irrelevant to the component if the component has access to the stream. Better to keep it private, and have a function within the same service to update the BehaviorSubject
's value if needed.
For example..
In a service:
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
@Injectable({ providedIn: 'root'})
export class ExampleService {
private exampleSource = new BehaviorSubject<any>(null);
example$: Observable<any> = this.exampleSource.asObservable();
updateSource(value: any) {
this.selectedDateSource.next(value);
}
}
We're defining a BehaviorSubject
with the initial value of null
.
Then we're defining example$
as the Observable (garden hose) to contain the stream which the BehaviorSubject
emits.
A function can update the .next()
value for the BehaviorSubject
, at which point example$
will be transporting the new value through the stream, and each subscription
will passively see its new value.
Then, in a component:
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
@Component({
template: `
<div>{{ exampleData$ | async }}</div>
`,
})
export class ExampleComponent implements OnInit {
exampleData$: Observable<any>;
constructor(private exampleService: ExampleService) {}
ngOnInit() {
this.exampleData$ = this.exampleService.example$;
}
}
Additionally, you can manually subscribe
to the observable in your component, however there are a few more steps to follow, and you'll have to manually unsubscribe
to the observable as well.
This functionality is effectively contained in Angular's built in async
pipe used in the previous example.
This component example yields the same result as the previous example:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
@Component({
template: `
<div>{{ exampleData }}</div>
`,
})
export class ExampleComponent implements OnInit, OnDestroy {
exampleData: any;
exampleData$: Observable<any>;
exampleDataSubscription: Subscription;
constructor(private exampleService: ExampleService) {}
ngOnInit() {
this.exampleData$ = this.exampleService.example$;
this.exampleDataSubscription = this.exampleData$.subscribe(data => {
this.exampleData = data; // <-- your data from the BehaviorSubject
});
}
ngOnDestroy() {
this.exampleDataSubscription.unsubscribe();
}
}
Hope this helps.