So I have a checkbox that is tied to a model:
<mat-checkbox [(ngModel)]="element[column]" (click)="updateRow(element, $event)"></mat-checkbox>
The click event is supposed to call the server and perform a write in the db.
So, for this, I thought that using concatMap was enough since it waits before making the next call to avoid exhausting the database.
updateRow(row, $event): void {
Observable.from([{ row: row }])
.concatMap(i => this._definitionService.updateDefinition(i) //async work
.subscribe(result => console.log('row updated'), error => {
this.snack(error);
});
}
The problem is that it does not wait since every event change is a new call, and performing too many changes exhaust the database.
What would be the solution for this? I thought about tying the events using fromEvent like this:
Observable.fromEvent($event.target, 'click')
.subscribe(i => {
console.log(i);
});
But, it only works when clicking twice on the checkbox.
What would be the correct approach?
Thanks
The reason why it's calling many requests is because every time you click the element you call updateRow that creates new instance of Observable.from. So concatMap won't help you because you when you make 10 clicks it subscribes to 10 different Observables with 10 different concatMaps.
Probably the easiest way to achieve what you want is creating an intermediate Subject and pushing item into it every time you call updateRow:
const s = new Subject();
s.concatMap(i => this._definitionService.updateDefinition(i))
.subscribe(
result => console.log('row updated'),
error => this.snack(error),
);
...
updateRow(row, $event): void {
s.next({ row });
}
Now you'll have just one subscription and pushing multiple item into Subject s (with next()) will execute them in order one after another.
Related
I am new on angular and stuck in a scenario where on page load I have to trigger 4 different APIs on ngOnit and have a click event on the same page while clicking on the event I want that it will stop all the previous calls, and makes new API call.
Code.
ngOnInit(): void {
this.getData('');
}
getData(data) {
const amount$ = this.service.getAmount(data).pipe(takeUntil(this.unSubscribe$));
const rate$ = this.service.getRate(data).pipe(takeUntil(this.unSubscribe$));
const applications$ = this.service.getApp(data).pipe(takeUntil(this.unSubscribe$));
const statistics$ = this.service.getStat(data).pipe(takeUntil(this.unSubscribe$));
applications$.subscribe(res => {
if (res.success) {
let d = res.dataSet;
}
}, (err) => {
})
------ and another three subscribe here
}
HTML
<button type="button" (click)="getData('abc')"
>Event</button>
'abc' is a dynamic string here that changes as per the need and then click on the button we pass the unique string in the getData function and on the basis of that string we hit a new API call on every click on the button but I want on every click it will stop all the previous APIs call and hit new one.
This is the typical scenario for switchMap operator.
What I would do is something like this
// define a BehaviorSubject, i.e. a subject that emits a first vale as soon as
// it is subscribed. In this case it emits an empty string, as in the ngOnInit
start$ = new BehaviorSubject<string>('')
// then define the Observable that runs the APIs with start$ as its starting point
execute$ = start$.pipe(
// here you place switchMap which means: as soon as I get a value from
// upstream, I unsubscribe and preceding subscription and start a new subscription
switchMap(val => {
// with merge here I run all the 3 APIs concurrently
return merge(
this.amount$(val),
this.rate$(val),
this.applications$(val),
this.statistics(val)$
)
})
)
Each Observable that runs the API should be returned by a function that takes a string as input, so that we can use the value passed by the button click, like this
amount$ = (data) => {
return this.service.getAmount(data);
};
If this is an http call, you do not need the takeUntil and the unsubscription logic, since the http client that Angular provides either emits once and then immediately complete or errors, so unsubscription is not needed.
Now in ngOnInit you create the one, and only one, subcription, like this
ngOnInit(): void {
this.execute$.subscribe(// whatever is necessary)
}
and in the handler of the button click event, you next the start$ Subject, i.e. you make the start$ emit the string you want
<button type="button" (click)="start$.next('abc')"
>Event</button>
You can find an example (with http calls simulated) here.
Button show triggered on ngIf in template. After this code the button is not shown.
public navBackVisible = false
ngOnInit() {
this.router.events
//.pipe(filter(event => event instanceof NavigationEnd))
//.pipe(pairwise())
.subscribe(
() => this.navBackVisible = true); // here true
console.log(this.navBackVisible); // here false
}
template:
<button *ngIf = "navBackVisible"><icon>arrow_back</icon></button>
UPD:
this is all code
ChangeDetection.Default
router emmits events
function inside subscribe is called
function inside subscribe fires before ngIf
But this.http.get(url).subscribe(() => this.navBackVisible = true) works fine
everything works if I write the same logic in the constructor of some service and call it from this component
Pairwise (https://rxjs.dev/api/operators/pairwise), only emitting value(s) if and only if after at least one value has been emitted, and router events here is only emitting 1 value and then completed.
You can not use pairwise() and filter(event => event instanceof NavigationEnd) at the same time. Choose one or the other because pairwise emits previous and current value in pairs. If you filter out the last router event (which is NaviationEnd event) your pairwise never emits and you never fall into function to turn this.navBackVisible = true. At least, that's my understanding of this (if I am mistaken please correct me).
Use:
this.router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe(() => {
console.log('i am here');
this.navBackVisible = true;
});
As a result, ngOnInit called the getter variable from the service-class. And in the service in the constructor, I used the same logic. Everything works. A little later I changed the variable to sessionStorage (it fits my logic). I still don't know what it was. If this happens to someone, then this is a good step towards finding a problem. This solution satisfied my problem, but the question is still open, I will follow the answers and answer your clarifications.
I have an issue which I have been figuring out for a while now.
I have this piece of code:
this.service.process(this.Object.id, this.Object.userId,this.pageNr)
.subscribe((data) => {
this.processed.emit(this.pageNr);
});
Now when debugging I see that the process method is called, when I refresh in the middle of the process, I do not see the processed.emit().
So, is it possible that on refresh the process( which is one transaction) goes through, but subscribed "processed" does not happen?
If someone could give a link to more information that would be awesome, or maybe information about how to avoid this.
Try this to avoid multiple subscription:
subscription:Subscription
...
if(!!this.subscription){
// unsubsribe from old subscription
this.subscription.unsubscribe();
}
this.subscription = this.service.process(this.Object.id, this.Object.userId,this.pageNr)
.subscribe((data) => {
this.processed.emit(this.pageNr);
});
i have the following code with Pusher:
Echo.private("channel").listen(".foobar", (e) => {
this.fetchData();
});
When there is an incoming request, I want data to be fetched again. But not on every Pusher event. I want the data to be fetched only once in 5 seconds.
I was thinking about a interval that is reset when there is a new incoming event, but how to achieve this?
Ok, first of all it's a Laravel Echo code. It may be using pusher behind the scenes, but saying it's a pusher code is slightly incorrect.
Then, if I understand you right, you need some kind of debounce function. For example lodash offers one.
const debouncedFunction = _.debounce(() => {
this.fetchData()
}, 5000);
Echo.private('channel').listen('.foobar', e => {
debounced()
})
debounce will create debounced version of function which you can then call, or even cancel, because it comes with cancel method.
debouncedFunction.cancel()
I am writing a webpage with the following structure:
One section (table A) depends on another section (table B);
Another section (table B) has elements that require recalculation on each update. The calculation is handled by external tools, and will cause an event when finished.
In order to guarantee correctness, the table need to be updated only after the other table is fully updated (i.e., done with computation). However, I don't know how to effectively achieve this, and I could not find any wait facility within JavaScript.
For now, I am using the following method:
Declare a global variable updated and make it false;
After the first table received input, I make an empty while loop until updated is true;
Add an listener, once the calculation is done and the event received, set updated to true.
This seems unintuitive to me but I cannot think of any other way of doing it. Is there any good ways to do this?
Thanks for any inputs!
In 2022, it's useful to have an event listener that fires off a Promise (which can be used in promise-chains, or async/await code). A clean way to make one:
function getPromiseFromEvent(item, event) {
return new Promise((resolve) => {
const listener = () => {
item.removeEventListener(event, listener);
resolve();
}
item.addEventListener(event, listener);
})
}
async function waitForButtonClick() {
const div = document.querySelector("div")
const button = document.querySelector("button")
div.innerText = "Waiting for you to press the button"
await getPromiseFromEvent(button, "click")
div.innerText = "The button was pressed!"
}
waitForButtonClick()
<button>ClickMe</button>
<div></div>
Add an listener, once the calculation is done and the event received, set updated to true.
Instead of setting updated to true, and then waiting for updated to be true- just do whatever you want to do in the listener.
myEventBus.addListener(function () {
// do whatever
updateTable();
alert('table updated!');
});
Doing empty while loops is a bad idea. Not only do you burn CPU cycles, but Javacript is single threaded so you will loop forever without giving anyone a chance to change the variable.
What you can do is rewrite the table that has other people depending on it to "fire an event itself". There are many ways to do this, but basicaly you just want it to call a "continuation' function instead of blindily returning. This function can be predefined or you can pass it as a parameter somewhere.
//this is just illustrative
//Your actual code will be probably very different from this.
function update_part(){
//do something
signal_finished_part()
}
var parts_done = 0;
function signal_finished_part(){
parts_done ++;
if(parts_done >= 5){
signal_all_parts_done();
}
}
function signal_all_parts_done()
{
//do something to table A
}
You could write a callback function for whatever triggers the update. To avoid messy callbacks, you could use promises too, and update parts of the table depending on the data retrieved in the update operation. Open to suggestions.