AngularJS / IonicFramework prevent multiple API calls on toggle - javascript

I've read this and am trying to implement similar functionality
"If you’re using toggle buttons, e.g.: to turn something on and off and it hits an API endpoint, be sure to wrap that interaction in a timer so you’re not calling that endpoint multiple times if the user decides to tap the control multiple times in quick succession. Wait a second, then hit the endpoint."
I have a settings page with a series of toggle switches. Rather than do an API call at the change of each and every toggle I want to delay the API call that passes the value of the scope to my server. I want to delay it by X seconds. However, by doing this
$scope.settingsChange = function () {
$timeout(function () {
//save_notifications
console.log('called');
// Trigger API call
}, 3000);
};
All that's happening its just delaying the call by 3000ms. I still get the console log appear 3 times if I toggle something 3 times.
I'm fully aware this is because I've implemented a timeout function. I'm not sure what to implement to get the functionality the poster alludes to in my quote above.
Any ideas much appreciated.
Thanks,
Note: my template looks like this
<ion-toggle ng-repeat="item in settingsData"
ng-model="item.checked"
ng-checked="item.checked"
ng-change="settingsChange()">
{{ item.text }}
</ion-toggle>

once you initiate the API call, block any other calls from being made.
$scope.settingsChange = function () {
if ($scope.processingSettingChange) return;
$scope.processingSettingChange = true;
$timeout(function () {
//save_notifications
console.log('called');
// Trigger API call
$scope.processingSettingChange = false;
}, 3000);
};
you might also want to disable the UI while the API is processing the request. You should be able to using the same $scope variable and ng-disabled

Related

Angular, to many API calls on input 'on-change'

I am using angular 8.
There is one auto-complete input and if it's value changes I have to make API call and load new suggestions for this input.
//In Template
<autocomplate [suggestions]="suggestions" (filterChange)="filterChange($event)"></autocomplate>
//In Component
filterChange(e) {
console.log(e)
this.loadSubscriptions(e ? { 'filterItem.name': e } : {})
}
loadSubscriptions(params) {
if (this.suggestionsSubscriber) this.suggestionsSubscriber.unsubscribe()
this.suggestionsSubscriber = this.suggestionsService.loadData(params).subscribe(
data => this.suggestions = data
})
}
Everything works fine, but the problem is when user types fast application makes to many requests.
Can I somehow delay requests if user types fast? for example, while the user is typing don't make API calls on every change, and if the user stops typing then make API call.
Or if you have a better way to solve this problem, please share.
Use RXJS denounceTime operator. Simply chain it to your Observable.
Whenever debounceTime receives an event, it waits a designated amount of time to see if another event comes down the pipe. If it does, it restarts its timer. When enough time has passed without another event streaming in, it emits the latest event.
I would suggest you to use throttle or debounce. You can write your own implementation for those or use library such as lodash.
Debounce using latest Rxjs can be a work around. Please see below for implementation.
Angular and debounce
I also had a same problem, so i put my code inside setTimeout as below
filterChange(e) {
console.log(e)
setTimeout(()=>{
this.loadSubscriptions(e ? { 'filterItem.name': e } : {})
},2000);
}
Now if you type very fast then it will not call the loadSubscriptions at that time. it will call after 2 sec.
You can configure the time according to your choice.
I hope This will helps you.

What event should I use in a User Control on a form refresh/requisition on Genexus?

Whenever I need to make an ajax request to the server without refreshing the page, Genexus should emit some event that I could use in my UC.
What exactly would be this event, and if there is none, how could I know every events in JS to use in an Genexus User Control?
I.e.:
If I click in an UserAction that may search for values in another table and then retrieve then for me, how can I capture this?
Another Scenario:
I'm making a request to the server and retrieving information of it. I need to get the
$(gx-warning-message).text();
but if my request is still loading, when I execute my function, it will return nothing, because the event is still loading up informations.
To avoid this, I'm making a looping, but it's not an elegant way to solve the issue.
$(".Button").click(function() {
var timesRun = 0;
var interval = setInterval(function(){
timesRun += 1;
if(timesRun === 60){
clearInterval(interval);
}
if ($(".gx-warning-message").text().length > 0) {
toastrgx();
$(".gx-warning-message").text('');
}
}, 200);
});
So, how can I make it better?
Thanks
If you want to be notified every time a GeneXus user event is fired, you can subscribe to gx.onafterevent event. For example:
gx.fx.obs.addObserver('gx.onafterevent', scope, function () {
// Here goes the code you want to execute
// every time a GeneXus event is fired
});
scope is the object you want to set as the this of the third parameter function.

How to delay a function excecution

I have this situation, i'm building a shopping cart, and i need to make a http request each time a product is added or its quantity is changed.
I have one button to add 1 unit of a product to the cart, it supposed that each unit added should make the http request, but i want to avoid to make to many calls at once... So i wanted to set a timeout for the request, but if within 3 secs the user clicks again, then i should cancel the last request with old data and then set a new timeout fot the request with the new data.
So i came up with this solution
function doRequest(){
clearTimeout(state.request);
state.request = setTimeout(updateCartService, 180);
}
The function is inside another function that retrieves the state object (A collection of variables and data persistent through all the application). The updateCarteService function contains the http request.
However all the requests are still being made, if i click 5 times the button that triggers doRequest, the call i being excecuted 5 times.
Have any idea of what could be wrong or pherhaps a better aproach to achieve my goal?
If it helps i'm bulding the site using vueJs, vuex (flux) and this code is inside an action.
setTimeout takes milliseconds, so you set timout for 180ms, it is too short, try 3000 for example. Here a small jsfiddle example.
https://jsfiddle.net/ShinShil/5fw2c63o/
var timeout;
$('#click').click(function() {
clearTimeout(timeout);
timeout = setTimeout(setText, 3000);
});
function setText() {
$('#text').append('text');
}

handle multiple angular calls when more submit buttons available

I have a form which contains one drop down, check box and four buttons. When any of the action is performed (check/uncheck or drop down selection or button click), it has to trigger a service call and the below section should be updated. There may be a chance that the multiple actions can be performed one after other immediately. If this is the case, http call should be triggered only after last activity.
Would be nice if there is any workable idea for this. I feel the timeout can helpful to wait for user to complete all activities (waiting for certain time after each activity) and call http service.
I guess thats what you need:
var timeoutPromise;
var delayInMs = 2000;
$scope.$watch("your_form_scope", function(newValue, oldValue) {
$timeout.cancel(timeoutPromise); //does nothing, if timeout alrdy done
timeoutPromise = $timeout(function(){ //Set timeout
//your code
},delayInMs);
});

Braintree multiple setup calls yield in multiple onPaymentMethodReceived events

I'm using angular, and in an angularUI modal window I want to show the Drop In form from Braintree to get a payment method. Thus, I create the usual form (partial.html):
<form id="creditCard" >
<div id="dropin"></div>
<button type="submit" id="btnPay" >Pay</button>
</form>
and then I show the modal with this:
var modalInstance = $modal.open({
templateUrl: 'partial.html',
controller: 'ModalController'
});
Where ModalController contains the call to the Braintree setup:
braintree.setup($scope.clientToken, 'dropin', {
container: 'dropin',
onPaymentMethodReceived: function (result) {
$scope.$apply(function() {
$scope.success = true;
// Do something else with result
});
}
});
This will show the Drop In form from braintree nicely (the setup generates the form) and accept the credit card and expiration date, all working fine so far.
The problem is, each time I call the modal, the ModalController is executed, and thus the braintree.setup() is also executed. Then, when I enter the credit card number and the expiration date and hit pay, the onPaymentMethodReceived() event is triggered once per setup execution! That is, if the first time I call the modal it will trigger the event once, the second time it will trigger it twice, and so on. Like if each time I call setup, a new hook to the event is created.
Any idea on how to avoid this? Is there a way to "unbind" the onPaymentMethodReceived() event handler? I do need to call the setup several times since each time I call the modal, the clientToken may have changed.
Thanks for any help or pointer to help.
Calling braintree.setup multiple times in angular seems unavoidable, either for the asker's reasons, or simply because setup is called in a controller that may be instantiated multiple times in a browsing session – like a cart or checkout controller.
You can do something like this:
$rootScope.success = false;
braintree.setup($scope.clientToken, 'dropin', {
container: 'dropin',
onPaymentMethodReceived: function (result) {
if(!$rootScope.success) {
$scope.$apply(function() {
$rootScope.success = true;
// Do something else with result
});
}
}
});
I found I wasn't able to avoid having the callback fire multiple times (the number of times seems to explode each time I revisit the view - yikes), but I could test whether I had performed my actions in response to the callback. Since the $scope will be destroyed if I leave the view, $scope.success is effectively reset when I need it to be. Because each new controller will have its own $scope, setting a success flag on the $scope may only halt additional executions on that $scope (which seems to still be available to the callback, even if the controller has been "destroyed"), so I found that using $rootScope meant only one execution total, even if I re-instantiated the controller multiple times. Setting $rootScope.success = false in the controller means that once the controller is loaded, the callback will succeed anew – once.
I think it's handled by the API since then with teardown:
In certain scenarios you may need to remove your braintree.js integration. This is common in single page applications, modal flows, and other situations where state management is a key factor. [...]
Invoking teardown will clean up any DOM nodes, event handlers, popups and/or iframes that have been created by the integration.
https://developers.braintreepayments.com/guides/client-sdk/javascript/v2#teardown
(I haven't tried it yet)
The link given by Arpad Tamas does not contain the info anymore. So I am posting the info given by BrainTree for posterity ;) Especially since it took me a few tries to find it with a Google search.
In certain scenarios you may need to remove your Braintree.js integration. This is common in single page applications, modal flows, and other situations where state management is a key factor. When calling braintree.setup, you can attach a callback to onReady which will provide an object containing a teardown method.
Invoking teardown will clean up any DOM nodes, event handlers, popups and/or iframes that have been created by the integration. Additionally, teardown accepts a callback which you can use to know when it is safe to proceed.
var checkout;
braintree.setup('CLIENT_TOKEN_FROM_SERVER', 'dropin', {
onReady: function (integration) {
checkout = integration;
}
});
// When you are ready to tear down your integration
checkout.teardown(function () {
checkout = null;
// braintree.setup can safely be run again!
});
You can only invoke teardown once per .setup call. If you happen to call this method while another teardown is in progress, you'll receive an error stating Cannot call teardown while in progress. Once completed, subsequent calls to teardown will throw an error with this message: Cannot teardown integration more than once.
I've wrapped this code in a function that I call each time the related checkout ionic view is entered.
$scope.$on('$ionicView.enter', function() {
ctrl.setBraintree(CLIENT_TOKEN_FROM_SERVER);
});
var checkout;
ctrl.setBrainTree = function (token) {
braintree.setup(token, "dropin", {
container: "dropin-container",
onReady: function (integration) {
checkout = integration;
$scope.$emit('BTReady');
},
onPaymentMethodReceived: function(result) {
...
},
onError: function(type) {
...
}
});
// Prevents a call to checkout when entering the view for the first time (not initialized yet).
if (checkout) {
// When you are ready to tear down your integration
checkout.teardown(function () {
checkout = null; // braintree.setup can safely be run again!
});
}
};

Categories