prevent user to click button until service response angular 7 rxjs - javascript

I am looking for efficient solution to avoid user to click the submit button again and again until they get response from backend API.
I have thought following solution but looking for any other ways to handle this.
use loader which will disable whole html until we get some response.
use exhaustmap to avoid multiple http calls
use throttletime which will manually not allow user to click for specific time period.
Please guide me if there is any other solutions which we can use.

I would recommend to use exhaustMap() as primary solution in this case.
In template:
<button (click)="submitForm()">Submit</button>
then in your component:
submit$: Subject<boolean> = new Subject();
constructor() {
this.submit$.pipe(
exhaustMap(() => this.http.get("api/endpoint"))
).subscribe(res => {});
}
submitForm() {
this.submit$.next(true);
}
You click 'Submit' and all proceeding click events will be ignored by exhaustMap() operator until HttpClient instance returns response and get completed.

You can disable the button itself till you get a response by having a flag in your component's controller file.
// in component's controller
loading = false;
.
...
submit(): void {
loading = true;
this.someserive.getData().pipe(finalize(() => this.loading = false).subcribe(...
.
....
}
// in component's template
<button [disabled]="loading" (click)="submit()">Submit</button>

Related

How do I wait for users to click on certain buttons in component, then return a value from that in a function depending on which button was pressed?

I have an overlay component that appears when a user clicks on certain things in my page, and in this overlay it gives a warning and 2 buttons, one for yes and the other for no. What I want is to create a function that'll serve this component, and then it will wait for the user to respond, and subsequently return true or false based on what button was pressed. This boolean result can then be used to further progress to other code.
This is what I have tried already. It uses promises rather than rxjs observables.
A component will call this function to bring the overlay from the service, eg this.service.promptUser().then(res => if (res === true) { doSomething() }).
In the service:
didContinue: boolean = null;
async promptUser() {
this.showOverlay.next(true) //BehaviourSubject when true brings the popup
await waitForUser();
const decision = this.didContinue;
this.closeOverlay(); //sets didContinue back to null
return decision
}
The didContinue is a property inside of the service to indicate whether they have clicked yes or no using a boolean. Otherwise it will remain null. The click events from the overlay component will set the property didContinue to true or false.
The waitForUser function to wait for the user's input:
async waitForUser() {
while (this.didContinue === null) {setTimeout(() => {}, 50};
}
Currently it'll get stuck at the waitForUser() function but the popup will have not rendered at that stage, so the user can't input anything, the didContinue property will never change, and the application will freeze.
Please do send it forward if you know of an existing solution, I miss a lot of things with my google-foo. I am currently still new to Angular.
Create a service that will handle the result
inject service in your component where you want to use it like this
constructor(public popService: popService) { } // create functions accordingly in service
Call service method with the help of constructor on click() event like this
<button (click)="popService.success()">Done
<button (click)="popService.cancel()">Done
i hope this will help, let me know if you need further help :-)

Angular, RxJS - How to cancel previous requests using switchMap?

I was trying to figure out that for a long time by myself but I stuck.
My case: I have a table with data, when user enters the page the request goes to REST API. Above the table there is a form that specify/filters results of the table. When user clicks a button, new request with query parameters goes to a service.
I'm trying to find a way to stop a previous request when user clicked a button, to search a table with specific parameters
Here is what I have done so far stackblitz. Maybe my approach is wrong and someone could show me how to write it better, or maybe there are just few thing to correct.
Thank you for any help
You can use the takeUntil operator inside your pipe, and pass a subject to it, and everytime you apply a new filter you can emit something to that subject to unsubscribe from the previous subscription and subscribe again! let me give you an example on how to use this operator:
regardless if your code was working or not!! I added just the part on how to use the takeUntil operator!
...
unsubscribe = new Subject<boolean>();
constructor(private _service: ServiceConnectService) {}
ngOnInit() {
this.clickStream
.pipe(
map(() => console.log(1)),
switchMap(() => {
return this.getMethodServicesInstances();
}),
takeUntil(this.unsubscribe) // this will be executed once you call next with a value!! and the subscription will be stopped!
)
.subscribe(results => {
console.log(2);
});
}
applyFilter(){
this.unsubscribe.next(true);
/*
you can then rerun the subscription and reset the unsubscribe subject state to null!
*/
}
...

tap is not waiting for response from backend service call in angular

how to subscribe to response when tap operator is used in the service.
Have anyone know how to resolve this?
edit(status) {
dataObj.val = status;
// call post service with status..
this.service
.update(dataObj)
.pipe(takeUntil(this._ngUnsubscribe$))
.subscribe(() => {
//i would like to wait until response come from backend and then navigate to the page so i get data over there.
if (res.status === 'Success') {
this.router
.navigate(['../../success'], {
relativeTo: this.route,
})
.then(() => {});
} else {
this.location.back();
}
});
}
//akita store service
update(
obj: any,
): Observable < any > {
return this.service.update(obj).pipe(
delay(800),
map((data: RestfulResponse < any > ) => data.data),
tap((data: anny) => {
this.store.update((state) => {
state.updateValue = data; // value is not updating and it is navigating to route
});
}),
);
}
//post service
update(obj){
//post call
}
Is there any way I can use tap and in service side and subscribe on component side?
I know I can use finalize but it is not helping for writing conditions inside.
The tap operator, by design, handles side effects which don't happen within the context of your observable pipeline. This means that your pipeline will never wait for results from the tap itself. I don't recommend using it in this manner. Under most circumstances, I only use tap for debugging.
If you are waiting for a particular state change, you should create a separate observable, selecting from your store, to watch the state for the expected change.
If you want to trigger an additional action when something happens, I recommend using ngrx Effects to achieve this.
Have a look at this post, where I talked about how to implement a similar use case:
https://stackoverflow.com/a/64491398/166850
You should also strive to set up reducers that apply your state changes, rather than updating the store directly.
Consider each of the following as a separate concern that you can implement independently of the others:
When user does an edit, trigger an edit action.
The reducer should update the state based on the edit action (for example, to show that a save is in progress)
When the edit action is triggered, trigger an effect. The app should make an HTTP call to save the change, then trigger a save finished action.
When the save is finished, the router navigation should be triggered.
This separates your code into multiple units which are easy to test and verify independently.
If #1 produces an action which is consumed by your reducer (#2), you can also create an ngrx Effect for #3 which listens for the same action, handles the HTTP call using switchMap, then triggers another action to signal that it's done.
Edit
Here's a simple example. The first time an action called APP_LOADED is triggered (from the AppComponent), this Effect makes an HTTP call to get data from the server, then triggers an action using the response data as the action payload.
The actual HTTP call is delegated to another service, the HttpMyConfigDataService, which simply calls HttpClient and returns an Observable.
#Injectable({
providedIn: 'root'
})
export class LoadMyConfigEffect {
constructor(
private httpMyConfigDataService: HttpMyConfigDataService,
private action$: Actions
) {
}
loadMyConfigData$ = createEffect(() => {
return this.action$.pipe(
filter((action) => action.type === 'APP_LOADED'),
take(1),
switchMap(() => this.httpMyConfigDataService.get().pipe(
map(data => {
return {type: 'MY_CONFIG_DATA_LOADED', payload: data};
}),
catchError(err => {
console.error('Error loading config data.', err);
return of({type: 'CONFIG_LOAD_ERROR', payload: err.message, isError: true);
})
))
);
});
}

Page not refreshed when clicked delete button in angular

I have a page running at "http://localhost:4200/assignmentForAudit" and the UI looks like
When i click delete button then the data gets deleted but the page is not refreshed. I tried to put this.router.navigate(['/assignmentForAudit']); after delete operation but it is not refreshing the page.How can i achieve this refresh method so that the data gets removed?
method to delete in component.ts
onDelete(nas:any){
this.assignmentAudit.id=nas.assignmentAudit[0].id;
console.log(this.assignmentAudit.id);
if(window.confirm('Are sure you want to delete this item ?')){
this.assignmentAuditService.deleteGroupFromSelection(this.assignmentAudit.id)
.subscribe(
data => {
console.log(data);
},
error => console.log(error));
}
this.router.navigate(['/assignmentForAudit']);
}
assignment-audit.service.ts class method to call the api operation
deleteGroupFromSelection(id: number): Observable<any> {
return this.http.delete(`${this.baseUrl}/${id}`, { responseType: 'text' });
}
Delete operation is working fine but the problem is the page is not refreshing.
that's not recommended at all as a best practice when you using angular you can simple emit data list after every delete with that you update the data visually too but if you want such behavior.
First Case by reload the whole page you can do so after every delete
window.location.reload();
Second Case if you need just to reload the component you can work around that and achieve it by a hack (just to trick the component you navigate away and navigate again to it)
this.router.navigateByUrl('/DummyComponent', {skipLocationChange: true}).then(() => this.router.navigate(['Your actualComponent you want to reload']));
/DummyComponent could be a empty component you just gonna use it to trick the actual component you need to refresh/reload
Instead of trying to reload the page, call the http method which is used to populate the entries again on success of the delete http call.
this.assignmentAuditService.deleteGroupFromSelection(this.assignmentAudit.id)
.subscribe(
data => {
this.entries = this.http.get() // something like this. here this.entries refers to data that is used to populate html.
console.log(data);
},
error => console.log(error));
}
You can also use a BehaviorSubject to emit a value, listening to it, you can decide on calling this.entries = this.http.get() again.
You may need to use the 'onSameUrlNavigation' option in Angular routes.
RouterModule.forRoot(routes, {onSameUrlNavigation: ‘reload’})
Can your show how to binding array of object to template ? I thinks you just remove item from array then template it will update data too.
something like this:
this.nas.assignmentAudit = this.nas.assignmentAudit.filter(a => a.id !== this.assignmentAudit.id);

How to get the return state from a sendAction method in a component

I am not using Ember Data (I do not know if it solves the issue). I have a component and click the button will invoke an external event handler defined inside the controller. I would like to know the return state from a sendAction method. See image below for details.
enter text in textarea
click "BTN SAVE"
handle action in index controller
perform ajax request in btnPressed actions obj
MY QUESTION How to notify the component the return state from the step (4)
Once I know the request state I can perform the correspond action(s) in my component.
Thanks
You can't have return value from action if you're using old action handling mechanism(using sendAction()), what you need are closure actions. Using closure actions you can call action defined in controller directly in component and have return value from it.
Here's your example using closure actions.
index.hbs
{{note-editor btnPressed=(action 'btnPressed')}}
In note-editor.hbs
<button onclick={{action 'actionBtnPressed'}}>Save</button>
And then in note-editor.js you can call btnPressed when button is clicked.
actionBtnPressed() {
let val = this.btnPressed();
// val is value returned from controller's btnPressed action
}
You can read more about closure actions here.
https://github.com/emberjs/rfcs/blob/master/text/0050-improved-actions.md
After reading the post Correct way to make an ajax call from EmberJs component?,
I move the AJAX code inside the controller to the component to solve this issue.
actionBtnPressed() {
const card = {
front: this.get('cardFront'),
back: this.get('cardBack'),
tags: this.get('tags')
};
if (this._validateCardFront()) {
this.get('ajax').createCard(card)
.done((data) => {
this._cleanInputs();
})
.fail((err) => {
});
}
}
ajax is a service that is injected into the component.

Categories

Resources