Angular 11 reactive form subscribe to valuechanges of input subscribing infinitely - rxjs

I am trying to subscribe value changes of reactive form input control. but this is getting subscribed continuously. I am not sure what is wrong with my code. Can anyone please help me with this?
this.sectionForm
?.get('sectionText')
.valueChanges.pipe(debounceTime(1000))
.subscribe((_newVal) => { //getting subscribed continously
if (this.attributes?.sectionName != _newVal) {
this.attributes.sectionName = _newVal;
}
this._validatorFunctionsService.validateAllForms();
});

There is a good practice to use distinctUntilChanged with input controls.
this.sectionForm?.get('sectionText').valueChanges.pipe(
debounceTime(1000),
distinctUntilChanged(),
)
.subscribe((_newVal) => {
if (this.attributes?.sectionName != _newVal) {
this.attributes.sectionName = _newVal;
}
this._validatorFunctionsService.validateAllForms();
});

Related

how to subscribe to the last observable and execute a funtion in rxjs?

Hi I am having a small code snippet in my project .
it basically subscribe to an angular form group and calls a method generatePerformanceGraph that draws an svg.
the form group has around 6 form controls . the problem is some times when i change the value of a form control , it will set another values to another form controls. As a result when i change some form control value it causes generatePerformanceGraph to called multiple times . How can i prevent this problem .
in short basically what i want is when there is a change in form group ,I would like to subscribe to the last observable and then execute the generatePerformanceGraph once.
this.formGroup.valueChanges.subscribe(formValue => {
if(this.formGroup.valid) {
this.generatePerformanceGraph(formValue);
}
});
I have tried the following how ever it didnt work out well.
this.formGroup.valueChanges.
pipe(
distinctUntilChanged()
) .subscribe( formValue => {
if(this.formGroup.valid) {
this.generatePerformanceGraph(formValue);
}
});
Try debounceTime, the time is up to you in ms. The debounceTime ignores events that happen within 200ms of each other and only accept the last one.
https://www.learnrxjs.io/learn-rxjs/operators/filtering/debouncetime
this.formGroup.valueChanges.
pipe(
debounceTime(200),
) .subscribe( formValue => {
if(this.formGroup.valid) {
this.generatePerformanceGraph(formValue);
}
});
To go the distinctUntilChanged way, you have to do it a different way because formValue is an object.
this.formGroup.valueChanges.
pipe(
distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)),
) .subscribe( formValue => {
if(this.formGroup.valid) {
this.generatePerformanceGraph(formValue);
}
});

RxJS Unsubscribe Only From Inner Observable

Let's say I have an interval that each second sends an heartbeat. At each beat i'd like to inspect something on my web page and react accordingly. I'd also like the option to unsubscribe from the inner Observables actions, but keep getting the heartbeat so when i subscribe back, everything will flow as before.
Creating a Subscription from Interval and piping it leaves no option to unsubscribe from the inner action, but only the whole subscription as whole.
Is there a way to return the inner Observable so i can unsubscribe from it while still retaining the heartbeat created from the Interval?
Edit: I've tried to create a class to describe what I'm talking about:
class Monitor {
sub: Subscription | null = null;
start() {
this.sub = this.monitor().subscribe();
}
monitor() {
const dom$ = someSelectorObserver(this.win.document, '#someSelector').pipe(
mergeMap(newElementOrBail => {
if (newElementOrBail) {
return handle(newElementOrBail);
} else {
return bail();
}
}),
tap({
error: error => this.log.error(error),
}),
);
return dom$;
}
handle(ele: HTMLElement) {
// do stuff
}
bail() {
this.sub.unsubscribe();
}
}
So basically my monitor starts with creating the subscription, as long as there's a new element to handle everything is fine, but when a bail signal appears I'd like to unsubscribe while still monitoring the DOM changes for a return of the previous elements.
So the outer subscription is basically the DOM observer and the inner is the mergeMap handle function. Does it make more sense?
You could just put some conditional on your inner observable:
private takeSignal = true
interval(3000).pipe(switchMap(() => takeSignal ? inner$ : NEVER))
Then just flip takeSignal as needed.
But it seems easier to just unsubscribe from the whole thing and resubscribe when needed. Why keep the interval going when you’re not using it?
You can split your logic in two (or more) streams.
Store heartbeat$ in a separate variable and subscribe to multiple times for different reasons.
In this way, you'd be able to split your logic into different streams and control subscriptions individually.
const heartbeat$ = interval(3000);
const inspectWeb = heartbeat$.pipe(
// do stuff
).subscribe()
inspectWeb.unsubscribe()
heartbeat$.pipe(
// do other stuff
).subscribe()

How do I refactor a traditional synchronous loop with RxJS?

I'm new to RxJS and trying to wrap my brain around how I should be writing my code. I'm trying to write a function that extends an existing http which returns an observable array of data. I'd like to then loop over the array and make an http request on each object and return the new array with the modified data.
Here's what I have so far:
private mapEligibilitiesToBulk(bulkWarranties: Observable<any[]>): Observable<IDevice[]> {
const warranties: IDevice[] = [];
bulkWarranties.subscribe((bulk: any[]) => {
for (let warranty of bulk) {
// Check if another device already has the information
const foundIndex = warranties.findIndex((extended: IDevice) => {
try {
return warranty.device.stockKeepingId.equals(extended.part.partNumber);
} catch (err) {
return false;
}
});
// Fetch the information if not
if (foundIndex > -1) {
warranty.eligibilityOptions = warranties[foundIndex];
} else {
this.getDevices(warranty.device.deviceId.serialNumber).subscribe((devices: IDevice[]) => {
warranty = devices[0];
}); // http request that returns an observable of IDevice
}
warranties.push(warranty);
}
});
return observableOf(warranties);
}
Currently, my code returns an observable array immediately, however, its empty and doesn't react the way I'd like. Any advice or recommended reading would be greatly appreciated!
Without knowing a lot more about your data and what would make sense, it is impossible to give you the exact code you would need. However, I made some assumptions and put together this StackBlitz to show one possible way to approach this. The big assumption here is that the data is groupable and what you are actually trying to achieve is making only a single http call for each unique warranty.device.stockKeepingId.
I offer this code as a starting point for you, in the hopes it gets you a little closer to what you are trying to achieve. From the StackBlitz, here is the relevant method:
public mapEligibilitiesToBulk(bulk: Warranty[]): Observable<IDevice[]> {
return from(bulk).pipe(
tap(warranty => console.log('in tap - warranty is ', warranty)),
groupBy(warranty => warranty.device.stockKeepingId),
mergeMap(group$ => group$.pipe(reduce((acc, cur) => [...acc, cur], []))),
tap(group => console.log('in tap - group is ', group)),
concatMap(group => this.getDevices(group[0].device.deviceId.serialNumber)),
tap(device => console.log('in tap - got this device back from api: ', device)),
toArray()
)
}
A couple of things to note:
Be sure to open up the console to see the results.
I changed the first parameter to an array rather than an observable, assuming you need a complete array to start with. Let me know if you want this to extend an existing observable, that is quite simple to achieve.
I put in some tap()s so you can see what the code does at two of the important points.
In the StackBlitz currently the getDevices() returns the same thing for every call, I did this for simplicity in mocking, not because I believe it would function that way. :)

rxjs operator to define logic after subscribe call

const source = Rx.Observable.of(1);
const example = source
.do(val => console.log('do called'));
example.subscribe(val => console.log('subscribe called'));
//Output :
do called
subscribe called
This exemple shows that do is executed before subscribe.
Which operator do I need to use to define logic after subscribe is executed ?
I need this to define logic one time and that must be executed after each subscribe call that helps also to respect SRP (Single responsibility Principle) an example is to handle caching logic in interceptor using some kind of specific operator that I am looking for and subscribe in services
The way I handle an Interceptor is as follows, it may help if I understand your requirements correctly.
...
private interceptor(observable: Observable<Response>): Observable<Response> {
return observable
.map(res => {
return res;
})
.catch((err) => {
//handle Specific Error
return Observable.throw(err);
})
.finally(() => {
//After the request;
console.info("After the Request")
});
}
protected get(req: getHttpParams): Observable<Response> {
return this.interceptor(this.httpClient.get(`${path}/${String(req.id)}`, req.options));
}
...
I would also recommend taking a look at Angular 5's in-built interceptor for http requests specifically

Angular2: Example with multiple http calls (typeahead) with observables

So I am working on couple of cases in my app where I need the following to happen
When event triggered, do the following
List item
check if the data with that context is already cached, serve cached
if no cache, debounce 500ms
check if other http calls are running (for the same context) and kill them
make http call
On success cache and update/replace model data
Pretty much standard when it comes to typeahead functionality
I would like to use observables with this... in the way, I can cancel them if previous calls are running
any good tutorials on that? I was looking around, couldn't find anything remotely up to date
OK, to give you some clue what I did now:
onChartSelection(chart: any){
let date1:any, date2:any;
try{
date1 = Math.round(chart.xAxis[0].min);
date2 = Math.round(chart.xAxis[0].max);
let data = this.tableService.getCachedChartData(this.currentTable, date1, date2);
if(data){
this.table.data = data;
}else{
if(this.chartTableRes){
this.chartTableRes.unsubscribe();
}
this.chartTableRes = this.tableService.getChartTable(this.currentTable, date1, date2)
.subscribe(
data => {
console.log(data);
this.table.data = data;
this.chartTableRes = null;
},
error => {
console.log(error);
}
);
}
}catch(e){
throw e;
}
}
Missing debounce here
-- I ended up implementing lodash's debounce
import {debounce} from 'lodash';
...
onChartSelectionDebaunced: Function;
constructor(...){
...
this.onChartSelectionDebaunced = debounce(this.onChartSelection, 200);
}
For debaunce you can use Underscore.js. The function will look this way:
onChartSelection: Function = _.debounce((chart: any) => {
...
});
Regarding the cancelation of Observable, it is better to use Observable method share. In your case you should change the method getChartTable in your tableService by adding .share() to your Observable that you return.
This way there will be only one call done to the server even if you subscribe to it multiple times (without this every new subscription will invoke new call).
Take a look at: What is the correct way to share the result of an Angular 2 Http network call in RxJs 5?

Resources