I have a form and I allow the user to click as many times as he wants on a refresh button. Of course, I use debounceTime operator but I don't know how to:
either cancel the previous http requests
or indicate to my service to return the value of the latest emission.
For example:
t1: click => received data in 2000ms
t2: click => received data in 200ms
Therefore, I will get the data from t1 moment whereas the latest one is at t2.
I've tried with pipe(last()), switchMap but I don't return data.
My component:
this.filtersForm.valueChanges.pipe(debounceTime(500)).subscribe(
form => {
this.service.setFilters(form); // Set private field in service (1)
this.onSubmit();
}
);
onSubmit() {
if (this.filtersForm.valid) {
this.service.notifFiltersHasChanged();
}
}
Service:
ctor(...) {
this.filters$.subscribe(f => this.getData());
}
notifFiltersHasChanged() {
this.filters$.next(this._filters); // (1) _filters is set by setFilters method
}
getData(): void {
// ...
this.backEndService.getAll(this._filters).subscribe(data => this._data = data);
}
BackEndService:
getAll(filters: any): Observable<Data> {
return this.httpClient.get<Data>(url).pipe(last());
}
The main trick is to use a single subscription (or even zero, if you'll use | async pipe in your template). So you source from an Observable and chain through your services.
Heres an updated example of yours:
Component
onDestroy$ = new Subject<void>();
constructor(){
this.filtersForm.valueChanges.pipe(
// accept only valid values
filter(() => this.filtersForm.valid),
// debounce them
debounceTime(500),
// when a value comes in -- we switch to service request
// subsequent values would cancel this request
switchMap(formValues => this.service.getData(formValues)),
// this is needed to unsubscribe from the service
// when component is destroyed
takeUntil(this.onDestroy$)
)
.subscribe(data=>{
// do what you need with the data
})
}
ngOnDestroy() {
this.onDestroy$.next(void 0);
}
Service
// service becomes stateless
// its only responsible for parsing and passing data
getData(filters): Observable<Data> {
return this.backEndService.getAll(filters);
}
BackEndService
getAll(filters: any): Observable<Data> {
return this.httpClient.get<Data>(url).pipe(last());
}
Another way would be to have a Subject, that you would push to. Otherwise it would be the same chaining on top of that Subject.
Hope this helps
Related
In the method below I want to call two observables. After the data from first observable (getUnrecoveredGearsExt- a http req) is returned I want to pass the data to the second observable (createUpdate- persist to indexDB). Is there a cleaner way to achieve this maybe using some of the rxjs operators. thanks
Note: after the successful completion of the second observable I want to return the data from the first Observable. The use case is get data from the backend and store locally in indexDB and if successful return data or error
public getAndUpdateUnrecoveredGears(cfr: string, maxResults?: number, excludeTripid?: string) : Observable<GearSet[]> {
return Observable.create((observer) => {
this.getUnrecoveredGearsExt(cfr,maxResults,excludeTripid).subscribe(
(gears: GearSet[]) => {
this.createUpdate(gears).subscribe(
() => {
observer.next(gears);
observer.complete();
},
(error) => {
observer.error(error);
}
);
},
(error) => {
observer.error(error);
}
);
});
}
Having nested .subscribe() methods is an anti-pattern of RxJS and can cause many issues. So it's a strong signal of when you need to use operators. Fortunately, there is one which simplifies your code.
public getAndUpdateUnrecoveredGears(cfr: string, maxResults?: number, excludeTripid?: string) : Observable<GearSet[]> {
return this.getUnrecoveredGearsExt(cfr,maxResults,excludeTripid).pipe(
concatMap((gears:GearSet[])=>this.createUpdate(gears))
);
}
Because we're dealing with HTTP requests, they'll emit one value then complete. For this, we can use concatMap(). This operator will wait until getUnrecoveredGearsExt() completes, and then will subscribe to createUpdate() using the value emitted from getUnrecoveredGearsExt(). The operator will then emit any values coming from this "inner observable".
Assuming createUpdate() is also an HTTP request, it will automatically send a complete signal after emitting the response.
The solution below works for me. The final issue was how to pass the previous result 'gears' out as a final result. This is achieved by using the combineLatest to pass the two results to the next map operator, which can then pass gears out.
public getAndUpdateUnrecoveredGearsAlt2(cfr: string, maxResults?: number, excludeTripid?: string): Observable<GearSet[]> {
return this.getUnrecoveredGearsExt(cfr,maxResults,excludeTripid).pipe(
switchMap((gears: GearSet[]) => { return combineLatest(this.createUpdate(gears),of(gears));}),
map(([temp, gears]) => gears )
);
}
I am building a logger object that asynchronously obtains the IP address and then logs all values with this IP address. It must start collecting logs as early as it is instantiated, but emit them only after the IP address has been obtained; and after that it should emit as normal.
Here is my class:
class LoggerService {
constructor() {
let thisIp;
const getIp = Observable.create(function(observer) {
// doing it with a timeout to emulate bad network
setTimeout(() => {
fetch('https://api.ipify.org?format=json').then(response => response.json()).then(response => {
thisIp = response.ip;
console.log('fetched IP: ', thisIp);
observer.next(response.ip);
observer.complete();
});
}, 5000)
});
// this is where I plan to buffer logs until IP is obtained
this.logStream = new Subject().pipe(buffer(getIp));
// for starters - just log to the console with the IP address
this.logStream.subscribe((value) => console.log(thisIp, value));
}
emit = (message) => this.logStream.next(message);
}
But it does not work as I need; it does output all buffered values as an array but stops emitting them after the IP has been obtained:
const logger = new LoggerService();
setInterval(() => {
logger.emit('Hey ' + Math.random())
}, 1000);
// I get five messages and that's it
How do I make it emit my values even after buffering?
Update:
Looking back at this a year and a half later, I notice combineLatest/of aren't necessary; we can simply pipe the ip observable and map it to the desired shape. Here's how I would do it today:
export class LoggerService {
private messages$ = new Subject<string>();
private formattedMessages$ = this.messages$.pipe(
mergeMap(message => this.service.ipAddress$.pipe(
map(ip => `[${ip}] ${message}`)
))
);
constructor(private service: GenericService) {
this.formattedMessages$.subscribe(
message => console.log(message) // actual logging logic goes here...
);
}
public log(message: string) {
this.messages$.next(message);
}
}
Original Answer
You don't need to "buffer" the values per se, but you can rather create a stream that depends on the async ipAddress$, so the value won't get emitted until the ip address had been emitted. combineLatest will work well for this purpose.
Let's give the LoggerService a message stream called message$ and a simple log() method that pushes the provided string through this stream.
We can construct a stream of messagesWithIpAddresses$ that use combineLatest to create an observable that emits the provided message along with the ipAddress$, but only after both have actually emitted a value.
export class LoggerService {
private messages$ = new Subject<string>();
public log(message: string): void {
this.messages$.next(message);
}
constructor(service: GenericService) {
const messagesWithIpAddresses$ = this.messages$.pipe(
mergeMap(message => combineLatest(service.ipAddress$, of(message)))
);
messagesWithIpAddresses$.subscribe(
([ip, message]) => {
// actual logging logic would go here...
console.log(`[${ip}] ${message}`);
}
);
}
}
Since of(message) will emit immediately, we will just be waiting for ipAddress$. But, if a value has already been emitted, then it too will be immediate.
Check out this working StackBlitz
Update: below answer won't work. After viewing buffer documentation
your logger Subject() still won't emit anymore messages because it fires only when getIp fires. In your code, getIp fires only once, after the http request.
I think we need more details of what you want to achieve in order to propose a correct rxjs pipeline.
I would remove this line
observer.complete(); // remove it
That signals the subscriptor the stream is over, that's why you are not receiving any messages more.
So i have pretty straight forward scenario. One subject and observable. When client logs in i publish success, when user logs out i publish false.
Problem is in subscribe method in LoginComponent
First time everything works great. User logs in i get one event, but after that when user logs out second time and logs in again i get 2 same events, again if user logs out and then logs in i get 3 duplicate events and so on.
AuthService.ts
public _loggedIn: Subject<LoggedInOrResetPassword> = new Subject();
public loggedId: Observable<LoggedInOrResetPassword> = this._loggedIn.asObservable();
obtainAccessToken(){
// ommitted
this.httpClient.post(environment.baseUrl + url, null, requestOptions)
.subscribe(data => {
this.saveToken(data);
this._loggedIn.next(LoggedInOrResetPassword.createTrue());
});
// ommitted
}
private logout(navigateTo?: string){
this._loggedIn.next(LoggedInOrResetPassword.createFalse());
}
LoginComponent.ts
ngOnInit() {
this.authservice.loggedId.subscribe( ( loggedInOrResetPassword: LoggedInOrResetPassword ) => {
// HERE I GET DUPLICATE VALUES
});
The reason is that you are NOT unsubscribing when LoginComponent is destroyed.
Your code should be changed as follows
First add an instance property to LoginComponent to store the subscription, such as
export class LoginComponent implements OnInit, OnDestroy {
.....
loginSubscription: Subscription;
.....
}
Then change ngOnInit so that you store the subscription in the newly added property
ngOnInit() {
this.loginSubscription = this.authservice.loggedId.subscribe( ( loggedInOrResetPassword: LoggedInOrResetPassword ) => {
// HERE I GET DUPLICATE VALUES
});
Eventually add ngOnDestroy to make sure you unsubscribe when the component gets destroyed
ngOnDestroy {
if (this.loginSubscription) {
this.loginSubscription.unsubscribe();
}
}
Take a look at the async pipe of Angular as an alternative method to subscribe to Observables and automatically unsubscribe.
An Observable - a collection over time - is a useful thing to be able to request over the web. A feed is best modeled as an Observable, not a static array that you must poll and diff to request.
My question - if I wanted to create a web endpoint that would let you do
web-tail -f http://somewhere.com/biz-quotes
This service, queried by a fictional utility web-tail, would every 5 seconds a new pithy business quote like "Custom departmental synergy" would be returned. I could write such a web-tail utility with WebSockets, and establish a convention for what field of emitted objects would be emitted to the console. But what language would I write a consumable specification in?
Is the Observable specification mature enough to be referenced?
If your goal is to write a client which consumes messages sent by a server over websockets, you can definitely use RxJs on top of, say, socket.io.
This nice article explains you how this can work.
In a nutshell this is the TypeScript code you need.
import { Observable } from 'rxjs';
import { Subject } from 'rxjs';
import { Observer } from 'rxjs';
import * as socketIoClient from 'socket.io-client';
export class SocketObs {
private socket: SocketIOClient.Socket;
private connect = new Subject<any>();
private disconnect = new Subject<any>();
constructor(url: string);
constructor(input: any) {
this.socket = socketIoClient(input);
this.socket.on('connect',
() => {
this.connect.next();
// complete to make sure that this event is fired only once
this.connect.complete();
}
);
this.socket.on('disconnect',
() => {
this.disconnect.next();
// complete to make sure that this event is fired only once
this.disconnect.complete();
}
);
}
send(event, message?) {
this.socket.emit(event, message);
}
onEvent(event): Observable<any> {
return new Observable<any>((observer: Observer<any>) => {
this.socket.on(event, data => observer.next(data));
});
}
onDisconnect() {
return this.disconnect.asObservable();
}
onConnect() {
return this.connect.asObservable();
}
close() {
this.socket.close();
}
}
SocketObs class offers you the API you need in form of Observable, in particular onEvent returns an Observable which emits any time a certain event is received from the server.
Cancelling from the consumer side, might be called using takeUntil, but that's not necessarily very dynamic. In this case, though, I am looking to cancel an Observable from the producer side of the equation, in the same way you might wish to cancel a Promise inside a promise chain (which is not very possible with the native utility).
Say I have this Observable being returned from a method. (This Queue library is a simple persistent queue that read/writes to a text file, we need to lock read/writes so nothing gets corrupted).
Queue.prototype.readUnique = function () {
var ret = null;
var lockAcquired = false;
return this.obsEnqueue
.flatMap(() => acquireLock(this))
.flatMap(() => {
lockAcquired = true;
return removeOneLine(this)
})
.flatMap(val => {
ret = val; // this is not very good
return releaseLock(this);
})
.map(() => {
return JSON.parse(ret);
})
.catch(e => {
if (lockAcquired) {
return releaseLock(this);
}
else {
return genericObservable();
}
});
};
I have 2 different questions -
If I cannot acquire the lock, how can I "cancel" the observable, to just send back an empty Observable with no result(s)? Would I really have to do if/else logic in each return call to decide whether the current chain is cancelled and if so, return an empty Observable? By empty, I mean an Observable that simple fires onNext/onComplete without any possibility for errors and without any values for onNext. Technically, I don't think that's an empty Observable, so I am looking for what that is really called, if it exists.
If you look at this particular sequence of code:
.flatMap(() => acquireLock(this))
.flatMap(() => {
lockAcquired = true;
return removeOneLine(this)
})
.flatMap(val => {
ret = val;
return releaseLock(this);
})
.map(() => {
return JSON.parse(ret);
})
what I am doing is storing a reference to ret at the top of the method and then referencing it again a step later. What I am looking for is a way to pass the value fired from removeOneLine() to JSON.parse(), without having to set some state outside the chain (which is simply inelegant).
According to your definition of cancel, it is to prevent an observable from sending a value downstream. To prevent an observable from pushing a value, you can use filter:
It can be as simple as:
observable.filter(_ => lockAcquired)
This will only send a notification downstream if lockAcquired is true.
1) It depends on how your method acquireLock works - but I am assuming that it throws an error if it cannot acquire the lock, in that case you could create your stream with a catch and set the fallback stream to an empty one:
return Rx.Observable.catch(
removeLine$,
Rx.Observable.empty()
);
2) To spare the stateful external variable you could simply chain a mapTo:
let removeLine$ = acquireLock(this)
.flatMap(() => this.obsEnqueue
.flatMap(() => removeOneLine(this))
.flatMap(val => releaseLock(this).mapTo(val))
.map(val => JSON.parse(val))
.catch(() => releaseLock(this))
);