Angular Debounce within ValueChanges of form - javascript

I have a list called filteredUserNames which contains a lot of users. Everytime I change a value on my form it starts a new filter of the data. I know that to delay the time so that not every character triggers a new filter I need to use debounce, but I'm not sure where to add it. Should it be inside the value changes subscription? Also what is the correct way to implement it?
I have
searchString = new BehaviorSubject("");
searchString$ = this.searchString.asObservable();
In Constructor
this.myForm = this.fb.group({
searchString: [""],
});
this.myForm.controls.searchString.valueChanges.subscribe((val) => {
// SHOULD THE DEBOUNCE GO HERE ?? //
this.searchString.next(val);
}
In ngOnInit
this.searchString$.subscribe((searchTerm) => {
console.log(this.userNames);
if (this.userNames !== undefined) {
this.filteredUserNames = this.userNames.filter(
(userName) =>
userName.searchTerms
.toLowerCase()
.indexOf(searchTerm.toLowerCase()) !== -1
);
};
});

try this and you can add distinctUntilChanged to ignore similar values and tap operator for your side effects which is in your case emitting a new value to your behaviorSubject
import { tap, distinctUntilChanged, debounceTime} from 'rxjs/operators';
...
this.myForm.controls.searchString.valueChanges.pipe(
debounceTime(400),
distinctUntilChanged(),
tap((val) => this.searchString.next(val))
).subscribe()

Related

Why Rxjs combineLatest depends on both subscription and not call when one of them changed?

I have two subscriptions item as you see in the image:
one of them is search input another one is a selection filter.
I want to change the result when one or both of them change.
I use Rxjs combineLatest so when both of them or search input change, everything is ok, but when changing the Partner type at the first, the response does not change.
ngOnInit() {
super.ngOnInit();
this.getAllPartners();
combineLatest([this.searchPartnerFc.valueChanges, this.filterFc.valueChanges])
.pipe(
switchMap(([search, filter]) => {
let partnersBySearch = this.getSearchResult(search);
let partnersByFilter = this.getFilterResult(filter);
this.partners = partnersBySearch.filter(item => partnersByFilter.indexOf(item) > 0);
return this.partners;
}),
)
.subscribe();
}
getFilterResult(filterKey: string) {
if (filterKey == 'All') return this.allPartners;
else return this.allPartners.filter(partner => partner.partnerType == filterKey);
}
getSearchResult(searchString: string) {
return this.allPartners.filter(x => x.partnerName.toLowerCase().includes(searchString));
}
You can achieve your desired result by providing a default emission for each of your source observables using startWith:
combineLatest([
this.searchPartnerFc.valueChanges.pipe(startWith('')),
this.filterFc.valueChanges.pipe(startWith('All'))
]).pipe(
...
);
I think it's not working because of the nature of the combineLatest operator. It will only emit when both of the observables have emitted an initial value first. So when only the first observable emits, and the second doesn't, it will not do anything:
https://www.learnrxjs.io/learn-rxjs/operators/combination/combinelatest
I think you need a merge operator here:
https://www.learnrxjs.io/learn-rxjs/operators/combination/merge
Also please take a look here on this thread:
How to 'wait' for two observables in RxJS

Rxjs/lodash throttle - how to use it on condition and in case that the condition may change while app runing?

I'm listening to the observable that may return true or false value - the only thing that I want to do is to set throttleTime for function call when it's true and don't have it when it's false. So I did some kind of workaround for that but I don't like this solution. I have tried a different approach where I tried to do it in the actions' effect but without success..
So this is the observable:
this.store$
.pipe(
takeUntil(this.componentDestroyed$),
select(selectGlobalsFiltered([
GlobalPreferencesKeys.liveAircraftMovement])),
distinctUntilChanged()
)
.subscribe((globals) => {
if (globals && globals[GlobalPreferencesKeys.liveAircraftMovement] !== undefined) {
this.isLiveMovementEnabled = (globals[GlobalPreferencesKeys.liveAircraftMovement] === 'true');
}
if (!this.isLiveMovementEnabled) {
this.processPlaneData = throttle(this.processPlaneData, 4000);
} else {
this.processPlaneData = this.notThrottledFunction;
}
});
And as you can see I've created excat the same method that is 'pure' - notThrottledFunction and I'm assigning it when it's needed.
processPlaneData(data: Airplane[]): void {
this.store$.dispatch(addAllAirplanes({ airplanes: data }));
}
notThrottledFunction(data: Airplane[]): void {
this.store$.dispatch(addAllAirplanes({ airplanes: data }));
}
So basically this is working solution, but I'm pretty sure there is a better approach for doing such a things.
*throttle(this.processPlaneData, isLiveMovementEnabled ? 0 : 4000) doesn't work
So the second approch where I tried to do this inside of effect, I added a new argument for addAllAirplanes action - isLiveMovementEnabled: this.isLiveMovementEnabled
addAllAirplanes$ = createEffect(() =>
this.actions$.pipe(
ofType(ActionTypes.ADD_ALL_AIRPLANES),
map((data) => {
if (data.isLiveMovementEnabled) {
return addAllAirplanesSuccessWithThrottle(data);
} else {
return addAllAirplanesSuccess(data);
}
}
)
);
And then I added another effect for addAllAirplanesSuccessWithThrottle
addAllAirplanesThrottle$ = createEffect(() =>
this.actions$.pipe(
ofType(ActionTypes.ADD_ALL_AIRPLANES_THROTTLE),
throttleTime(4000),
map((data) => addAllAirplanesSuccess(data))
)
);
But it doesn't work. Can someone help me?
(It's not clear how the data arrives in your example code, but I'll assume an Observable)
throttle and throttleTime are similar to your use case, but I think it makes sense to build your own custom implementation without them. I'd suggest managing the timing yourself, using Date to determine time deltas.
You've already cached the live filtering boolean in the component state, so we can just handle all of the data stream from your position update observable and filter them out manually (which is all throttle does, but it expects you to be able to feed it an interval at subscription time, and yours needs to be dynamic).
Setup component scoped variables to contain previous timestamps, as
private prevTime: number;
private intervalLimit: number = 4000;
Supposing data$ is your input plane position data stream:
data$.pipe(filter(data => {
const now: number = Date.now();
const diff = now - this.prevTime;
if (this.isLiveMovementEnabled) {
// no throttle - pass every update, but prepare for disabling too
// record when we last allowed an update & allow the update
this.prevTime = now;
return true;
} else if (diff > intervalLimit) {
// we are throttling results, but this one gets through!
this.prevTime = now;
return true;
} else {
// we're throttling, and we're in the throttle period. eat the result!
return false;
}
}
Something like that gives you full control over the logic used whenever data comes in. You can add other operations like takeUntil and distinctUntilChanges into the pipe and trust that when you subscribe you'll be getting updated when you want them.
You can even adjust the intervalLimit to dynamically adjust the throttle period on the fly.

change the source of observable at runtime (rxjs)

i need to change the source of an observable with a swith
.
this.su = this.appService._sub1.subscribe(data => {
this.items.push(data);
});
//appService
setSub(name) {
if (name == 'A') {
console.log('B');
this._sub1 = this.sub2;
} else if (name == 'B') {
console.log('B');
this._sub1 = this.sub3;
}
console.log(this._sub1);
}
however, the source of the first observable keeps sending data, how can I do it?
Stackblitz link
https://stackblitz.com/edit/angular-ivy-evczcx?file=src/app/app.service.ts
Ultimately, I think you will want to use switchMap. You don't really want to switch the subscription, but rather the source.
It's cleaner if you design your service to expose observables, then have your components subscribe to them (ideally with AsyncPipe).
As a sample, something like this should work:
export class AppService {
private source$ = new BehaviorSubject<SourceType>('frutas');
private frutas = ['pera', 'manzana', 'platano', 'fresa'];
private electronicos = ['celular', 'pc', 'cable'];
private frutas$ = interval(2000).pipe(
map(n => n % 4),
map(i => this.frutas[i])
);
private electronicos$ = interval(2000).pipe(
map(n => n % 3),
map(i => this.electronicos[i])
);
data$ = this.source$.pipe(
switchMap(source => source === 'frutas' ? this.frutas$ : this.electronicos$)
);
setSource(name: SourceType) {
this.source$.next(name);
}
}
Here's a working StackBlitz demo.
Swapping a reference to a subscription does nothing to the subscription itself. You need to unsubscribe. Also keep in mind, that interval produces a "hot" observable. Meaning it will push data to the stream wether someone has subscribed or not.
In real life, you would probably use something like httpClient.get() which is not "hot" (runs only when subscribed to, and completes once it's done). So it's not an issue usually, but with interval, you will end up with memory leaks and performance hits unless you manually stop the scheduler from running.
this._sub1.unsubscribe();

RxJS, Observable, how to preserve value and switch map to another one

// ticker$ will update every 3s
// showHand$ will only triger after user click button
// I would like to take last ticker price as user order price when user click button
let lastPrice: number;
this.ticker$
// What I am doing now is preserve value to vairable here.
.do(ticker => lastPrice = ticker.closePrice)
.switchMap(() => this.showHand$)
.subscribe(showHand => {
// use value here
this.order.price = lastPrice;
this.order.amount = showHand.amount;
this.order.type = showHand.type;
this.submit();
});
Any segestion about how to prevser value and switch map together, without one line variable like above?
Results selector function is deprecated in version 6 will be removed in version 7.
From docs:
https://github.com/ReactiveX/rxjs/blob/master/docs_app/content/guide/v6/migration.md#result-selectors
with resultSelector (v5.x)
source.pipe(
switchMap(fn1, fn2)
)
the same functionality without resultSelector, achieved with inner map
source.pipe(
switchMap((a, i) => fn1(a, i).pipe(
map((b, ii) => fn2(a, b, i, ii))
)
)
The behaviour you require is already possible with an overload of SwitchMap with a selectorFunc for the combination of every (outerValue,innerValue):
this.ticker$
.switchMap(
() => this.showHand$,
(tickerValue, switchMap) => tickerValue
)
.subscribe(showHand => { });
There is a little hack to achieve this - basically you have a whole new observable inside the switchmap, and this observable has access to the value passed into the switchmap function. You can use this value in an inner map to preserve the value.
this.ticker$
.switchMap(ticker => this.showHand$.pipe(map(hand => ({ ticker,hand }) ))
.subscribe( obj => {
// use value here
this.order.price = obj.ticker;
this.order.amount = obj.hand.amount;
this.order.type = obj.hand.type;
this.submit();
});
I think this is the operator
this.showHand$.take(1)
.withLatestFrom(this.ticker$)
.subscribe(([showHand, ticker]) => {
this.order.price = ticker.closePrice;
this.order.amount = showHand.amount;
this.order.type = showHand.type;
this.submit();
});
Note, take(1) will close subscription, but if you want the user to be able to press the button many times, save the subscription to a const and unsubscribe when finished.

Angular Observable subscription fallback?

I'm using Angular 4 and I have a component that lives at the 'player/:id' route in my application. When the player navigates to this route from within the application I use a currentPlayerService to sync the current player within the app. This works great with the below code.
this.currentPlayerService.getPlayer()
.subscribe((player) => this.currentPlayer = player);
However, when the application loads directly to the 'player/:id' route (from an external link) I can comment out the above and use the code below to set the currentPlayer from the route params.
this.route.params.subscribe(params => {
this.playerService.getPlayer(params.id)
.subscribe((player) => {
this.currentPlayer = player;
this.currentPlayerService.setPlayer(player);
});
});
What I'd like to do (and am having difficulty finding the "reactive function programming" way of saying) is to load the player from the params only if this.currentPlayerService.getPlayer() doesn't have a value on load. I'm having a hard time conceiving of a way to use the Observable in some kind of logic control flow, so any help would be appreciated.
Thanks!
Observables itself is stateless. To keep track of a value (or current value), you will need to use either Subject or BehaviorSubject.
In your currentPlayerService, create a BehaviorSubject called playerBSubject:
export class CurrentPlayerService{
public playerBSubject: BehaviorSubject<any>;//can be type of Player if you have such type
constructor(){
this.playerBSubject = new BehaviorSubject({})//any value that you want to initialize
}
getPlayer(){
//do your logic here, whatever that is.
//I am using an Observable.of to mimic a http request
Observable.of({})
.subscribe((player)=>{
this.playerBSubject.next(player)//this will update the subject with the latest value
});
return this.playerBSubject.asObservable();
}
}
Note that the .next() method will update the values of your subject, and you can return it as an observable using .asObservable().
Now, in your component controller, you can check if your BehaviourSubject exist (or has the value you want), and only call playerService.getPlayer() if need to.
this.route.params.subscribe(params => {
//check if BehaviorSubject exist or not
if (this.currentPlayerService.playerBSubject.getValue() === {}) {
this.playerService.getPlayer(params.id)
.subscribe((player) => {
this.currentPlayer = player;
this.currentPlayerService.setPlayer(player);
});
}
});
Suggestions:
I am not sure why you need two service namely currentPlayerService and playerService. If your currentPlayerService is just to keep track of "current" value of the player, then you do not need it at all, should you use BehaviorSubject to keep track of your current player. All of them can boil down into one single service.
export class PlayerService {
public playerBSubject: BehaviorSubject<any>;
constructor() {
this.playerBSubject = new BehaviorSubject({})
}
getPlayer(id) {
Observable.of({id}) // implement your own logic
.subscribe((player) => {
this.playerBSubject.next(player)
});
return this.playerBSubject.asObservable();
}
setPlayer(id) {
return Observable.of({id})//implement your own logic
.subscribe(newPlayer => this.playerBSubject.next(newPlayer))
}
}
And in your controller if you want to get the current value you can just do:
this.currentPlayer = this.playerService.playerBSubject.getValue();
And with a little help of asObservable you can do this:
this.playerService
.asObservable()
.subscribe(player => {
if (player === {}) {
this.route.params.subscribe(params => {
this.playerService.getPlayer(params.id); //voila, your player is updated
})
}
//remember to update the value
this.currentPlayer = player
})

Categories