I'm wanting to implement an Observable / Subject with 3 particular attributes
Remember last emitted value and be able to surface it via a getter (BehaviorSubject)
Only emit when value changes
It must have a strong type such that the getter is known to be available by a consumer (aka. BehaviorSubject.getValue())
I'm thinking of just extending BehaviorSubject but want to make sure I'm not introducing any potential gotchas based on my novice understanding.
export class DistinctUntilChangedBehaviorSubject<T, TValue> extends BehaviorSubject<T> {
constructor(
initialValue: T,
private _distinctKeySelector?: (value: T) => TValue,
private _comparer?: _Comparer<TValue, boolean>
) {
super(initialValue);
}
public subscribe() {
// I'm particularly interested in knowing if this has any gotchas.
// Mostly things like creating subscriptions that don't get disposed as expected.
return super.distinctUntilChanged(
this._distinctKeySelector,
this._comparer
).subscribe.apply(this, arguments);
}
}
So 2 questions:
Does this seem like a reasonable approach / are there any gotchas here?
Is there another preferred way of doing this?
I do not know really why, but I tend to prefer composition over extension.
So I would do something along these lines
import {BehaviorSubject} from 'rxjs';
export class BehaviourSubjectAugmented<T> {
bs: BehaviorSubject<T>;
constructor(initialValue: T, private comparer: (p: T, q: T) => boolean) {
this.bs = new BehaviorSubject(initialValue);
}
getValue() {
return this.bs.getValue();
}
asObservable() {
return this.bs.asObservable()
.distinctUntilChanged(this.comparer);
}
complete() {
return this.bs.complete();
}
next(value: T) {
return this.bs.next(value);
}
}
Turns out my original idea causes a call stack exceeded issue. I'm assuming that distinctUntilChanged must call subscribe internally thus causing infinite recursion.
I ended up finding a simpler way to get what I needed by simply adding a method to an ISubject instance.
function distinctUntilChangedBehaviorSubject(
initialValue: number
): ISubject<number> & { getValue(): number } {
const observer = new BehaviorSubject<number>(initialValue);
const observable = observer.distinctUntilChanged();
const subject: ISubject<number> = Subject.create(
observer,
observable
);
return Object.assign(
subject,
{
getValue: () => observer.getValue()
}
);
}
Related
I have a class with more than 30 observable attributes. Each time my server receives a payload containing these 30 attributes I call the next() method for all the corresponding attributes of the instance, so far so good.
The problem is that, sometimes, I have to check for an attribute's value, outside the scope of the observer that subscribed to that observable attribute.
What comes to mind is that I have to have duplicate attributes for everything, one is the observable and the other one is a stateful attribute to save the arriving values for later consumption.
Is there some way to avoid this with a method like: Observable.getCurrentValue()?
As requested, some example code
class Example {
public subjects = {
a1: new Subject<any>(),
a2: new Subject<any>(),
a3: new Subject<any>(),
a4: new Subject<any>(),
a5: new Subject<any>()
}
public treatPayload(data: any) {
for (const prop in data) {
if (data.hasOwnProperty(prop) && prop in this.subjects){
Reflect.get(this.subjects, prop).next(data[prop])
}
}
}
public test() {
const a1_observable = this.subjects.a1.asObservable()
const a2_observable = this.subjects.a2.asObservable()
const example_payload_1 = {
a1: "first",
a2: "second",
a10: "useless"
}
const example_payload_2 = {
a1: "first-second",
a2: "second-second",
a10: "useless-second"
}
a1_observable.subscribe((a1_new_value: any) => {
const i_also_want_the_last_value_emitted_by_a2 = a2_observable.last_value() // of course, this doesn't exist
console.log(a1_new_value)
console.log(i_also_want_the_last_value_emitted_by_a2)
})
this.treatPayload(example_payload_1)
this.treatPayload(example_payload_2)
}
}
So, is there a way to retrieve the correct value of i_also_want_the_last_value_emitted_by_a2 without a pipe operator? I think it would be a problem to emit all values I could possibly use in a subscriber within a pipe of the a2_observable.
You could use BehaviorSubject.value, where you could store your server data.
Having read that you should never subscribe within another observable, I am having major difficulties understanding how to properly handle nested observables.
For every Candidate emitted, I want to match against multiple regular expressions, provided as an observable getPatterns$(). If one is found, it will be attached to the Candidate object.
class Candidate {
public name: string;
public matchingRegularExpression: RegExp;
}
listOfCandidates = [new Candidate('one'), new Candidate('two')];
private matchCandidates$(textToMatch: string): Observable<Candidate> {
return from(this.listOfCandidates)
.pipe(
map(f => {
f.regExp = this.getRegExp(f); // sequential
return f;
}),
map(cand: Candidate => {
this.getPatterns$().subscribe(patterns => {
if (....do some regexp matching...){
cand.matchingRegularExpression = pattern;
}
});
})
)
I tried using mergeMap or switchMap, but those seem to be used when you want to merge 1 outer object with n inner ones. But my inner observable should simply extend my Candidate object and emit 2 values in this example.
So first, it's considered as a bad practice because you completely lose the reference of your second subscription, which could result in a leak if the source continue emitting.
Fortunately we have an operator called switchMap, it allows us to switch on a new observable, and thus, avoid subscribing inside the observer function.
The main difference between switchMap and other flattening operators is the cancelling effect. On each emission the previous inner observable (the result of the function you supplied) is cancelled and the new observable is subscribed. You can remember this by the phrase switch to a new observable.
edit: add snippet
class Candidate {
public name: string;
public matchingRegularExpression: RegExp;
}
listOfCandidates = [new Candidate('one'), new Candidate('two')];
private matchCandidates$(textToMatch: string): Observable<Candidate> {
return from(this.listOfCandidates)
.pipe(
map(f => {
f.regExp = this.getRegExp(f); // sequential
return f;
}),
switchMap(cand: Candidate => {
return this.getPatterns$().pipe(
map(patterns => {
if (....do some regexp matching...){
cand.matchingRegularExpression = pattern;
}
return cand
})
)})
)
I think I have read 100+ posts on the topic, and I still cannot figure out how to chain two HttpClient calls using rxjs in Angular 6.
Let's say I have a service with that signature:
GeoService {
getState(): Observable<string> {
return this.http.get<string>(stateURL);
}
getCities(state: string): Observable<string[]> {
return this.http.get<string[]>(citiesURL + state);
}
}
I can't for the life of me figure out how to obtain both the state and the corresponding list of cities in my component:
import { Observable } from 'rxjs';
import { map, flatMap, mergeMap, filter, switchMap } from 'rxjs/operators';
...
ngOnInit() {
this.svc.getState().
pipe(map((state) => {
this.state = state;
return this.svc.getCities(state);
}),
mergeMap((cities) => this.cities = cities))
).
subscribe(console.log('done'));
The code above in one of my 20 random attempts at combining pipe/map/mergeMap/subscribe in every way I could think of... a working example would be really really appreciated :)
Thanks!
Edit: None of the "possible duplicate" posts contain an actual example that works
The 21st attempt would have been correct ;-)
this.svc.getState().
pipe(mergeMap((state) => {
this.state = state;
return this.svc.getCities(state);
}),
tap((cities) => this.cities = cities)))
.subscribe(() => console.log('done'));
The chained Observable goes inside mergeMap. You can think of it as:
First, map the incoming notifaction to an Observable, then merge the resulting "inner" Observable into the "outer" Observable
Also, use tap instead of map if you intend to change an outside state.
You were almost there:
this.svc.getState().
pipe(
mergeMap((state) => {
return this.svc.getCities(state).pipe(map(cities => {
return { state: state, cities: cities }
}));
}),
).subscribe(stateAndCities => console.log(stateAndCities));
I advise you to read this article:
https://blog.strongbrew.io/rxjs-best-practices-in-angular/#using-pure-functions
It also explains why you shouldnt interact with global variables in rxjs operators.
You can do something like this
this.svc.getState().pipe(
tap(state=>this.state=state),
switchMap(this.svc.getCities))
.subscribe(cities=>{
//got the cities
})
the map operator is here to transform the emited value, but the tap operator is used to do something without modifying emited value of the observable.
note that switchMap(this.svc.getCities) is equivalent to switchMap(state=>this.svc.getCities(state)
Hi i have a global service for several applications and i wish to make a method with several subscribes in order to stock and initialize all my datas and i wish make a subscribe to this method in my appComponent but i don't know how to make that
In my service
private initData(isLogged: boolean) {
this.http.get('/api/conf').subscribe(
conf => {
this.http.get('api/param').subscribe(
tokResp => {
this.appParams.token = tkResp.queoval;
this.appParams.culture = tkResp.culture;
this.appParams.GMT = tkResp.gmt;
this.http.get('/api/trad').subscribe(
trad => {
this.label = trad
// return an Observable
}
)
}
)
}
)
}
In my AppComponent
this.service.initData().subscribe(
result => {
this.test = result
}
How can i make that? I can't find the information in the documentation. Thank you for your help. It's important for my work, i used so much time to research for nothing :(
So since you want to make multiple async requests one after the other, you should use the observable function ".flatMap" (this is very similar to a Promises ".then"). The ".flatMap" function allows you to wait until the first request is completed before you continue.
So in your case, you would want your service to look something like this:
private initData(isLogged: boolean) {
return this.http.get('/api/conf').flatMap(
conf => {
return this.http.get('api/param');
}
).flatMap(
tokResp => {
this.appParams.token = tkResp.queoval;
this.appParams.culture = tkResp.culture;
this.appParams.GMT = tkResp.gmt;
return this.http.get('/api/trad');
}
).flatMap(
trad => {
this.label = trad;
return trad;
}
);
}
This function has all of the async requests chained together through ".flatMap" so that they are only called after the previous request completes.
The component file looks fine and should work with this new service.
As a general note, you should never subscribe to the observable inside
of the service. You should instead use functions like map, flatMap,
forkJoin ...
I am building a service which exposes an Observable. In this service I receive external function calls which should trigger a next call on the Observable so that various consumers get the subscribe event. During Observer constructor I can call next and everything works great, but how can I access this outside of the constructor so that external triggers can fire next calls?
private myObservable$: Observable<any>;
During service init I do
this.myObservable$ = new Observable(observer => {
observer.next("initial message");
}
Then in other methods of the same service I want to be able to execute something like
this.myObservable$.observer.next("next message");
The above obviously doesn't work, but how can I accomplish this goal?
I'm assuming I'm missing something basic since there must be a way to emit further messages outside of the Observable's initial constructor
You should create a Subject for that
this.myObservable$ = new Subject();
And then you can call at any point:
this.myObservable$.next(...);
Or use subscribe:
this.myObservable$.subscribe(...)
Actually Subject is used for both publisher and subscriber, and here I think you need only to publish your value, so simply use Observable.
By using observable, assign Subscriber to class level variable and then use it, like below code
subscriber: Subscriber<boolean>;
public observe(): Observable<boolean> {
return new Observable<boolean>(subs => {
this.subscriber = subs;
});
}
public callNext() {
if (this.subscriber) {
this.subscriber.next();
this.subscriber.complete();
}
}
Two ways:
Make myObservable$ public:
public myObservable$: Observable;
Encapsulate the observable in a subject stream, and provide a helper to call next:
export class TestService {
public myObservable$: Observable;
private _myObservableSubject: Subject;
constructor() {
this._myObservableSubject = new Subject();
this.myObservable$ = this._myObservableSubject.asObservable();
}
public NextMessage(message?: string): void {
this._myObservableSubject.next(message);
}
}
Observable: You have to call the next() function from inside the constructor and only one time you can subscribe
message = new Observable((observer)=>{
observer.next(9);
})
this.messsage.subscribe((res)=>{
console.log(res)
})
output: 9
Subject: You have to call next() function from outside the constructor and multiple times you can subscribe.
The subject does not store any initial value before subscribe.
messsage = new Subject()
this.messsage.next(3)
this.messsage.subscribe((res)=>{
console.log(' A '+res)
})
this.messsage.next(4)
this.messsage.next(5)
this.messsage.subscribe((res)=>{
console.log(' B '+res)
})
this.messsage.next(6)
output:
A 4
A 5
A 6
B 6
BehaviorSubject: You have to call next() function from outside the constructor and multiple times you can subscribe.
The BehaviorSubject does store only one initial value before subscribe.
messsage = new BehaviorSubject ()
this.messsage.next(3)
this.messsage.subscribe((res)=>{
console.log(' A '+res)
})
this.messsage.next(4)
this.messsage.next(5)
this.messsage.subscribe((res)=>{
console.log(' B '+res)
})
this.messsage.next(6)
output:
A 3
A 4
A 5
B 5
A 6
B 6
I ended up combining a couple of things:
olsn's answer, which nicely demonstrates Subject's ease of use
Ravi's answer, which correctly points out that we only want an Observable exposed
a more functional approach, because this and Class give me the shivers
TypeScript typings for generic use
const createObservableWithNext = <T>(): {
observable: Observable<T>;
next: (value: T) => void;
} => {
const subject = new Subject<T>();
const observable = subject.asObservable();
const next = (value: T) => subject.next(value);
return {
observable,
next,
};
};