I'd like to use angular-rx for a simple refresh button for results. If the user clicks the refresh button the results are reloaded. If the user clicks the the refresh button 100times in 1 second, only the latest results are loaded. If the results failed for some reason, that doesn't mean the refresh button should stop working.
To achieve the last point I'd like to keep a subscription (or resubscribe) even if it fails, but I can not work out how to do that?
This doesn't work, but here's a simple example where I try resubscribing on error:
var refreshObs = $scope.$createObservableFunction('refresh');
var doSubscribe = function () {
refreshObs
.select(function (x, idx, obs) {
// get the results.
// in here might throw an exception
})
.switch()
.subscribe(
function (x) { /*show the results*/ }, // on next
function (err) { // on error
doSubscribe(); // re-subscribe
},
function () { } // on complete
);
};
doSubscribe();
I figure this is common enough there should be some standard practice to achieve this?
UPDATE
Using the suggested solution, this is what I've made to test:
// using angularjs and the rx.lite.js library
var testCount = 0;
var obsSubject = new rx.Subject(); // note. rx is injected but is really Rx
$scope.refreshButton = function () { // click runs this
obsSubject.onNext();
};
obsSubject.map(function () {
testCount++;
if (testCount % 2 === 0) {
throw new Error("something to catch");
}
return 1;
})
.catch(function (e) {
return rx.Observable.return(1);
})
.subscribe(
function (x) {
// do something with results
});
And these are my test results:
Refresh button clicked
obsSubject.onNext() called
map function returns 1.
subscribe onNext is fired
Refresh button clicked
obsSubject.onNext() called
map function throw error
enters catch function
subscribe onNext is fired
Refresh button clicked
obsSubject.onNext() called
Nothing. I need to keep subscription
My understanding is that catch should keep the subscription, but my testing indicates it doesn't. Why?
Based on the context given in your comment, you want:
Every refresh button to trigger a 'get results'
Every error to be displayed to the user
You really do not need the resubscribing, it's an anti-pattern because code in Rx never depends on that, and the additional recursive call just confuses a reader. It also reminds us of callback hell.
In this case, you should:
Remove the doSubscribe() calls, because you don't need them. With that code, you already have the behavior that every refresh click will trigger a new 'get results'.
Replace select().switch() with .flatMap() (or .flatMapLatest()). When you do the select(), the result is a metastream (stream of streams), and you are using switch() to flatten the metastream into a stream. That's all what flatMap does, but in one operation only. You can also understand flatMap as .then() of JS Promises.
Include the operator .catch() which will treat your error, as in a catch block. The reason you can't get more results after an error happens, is that an Observable is always terminated on an error or on a 'complete' event. With the catch() operator, we can replace errors with sane events on the Observable, so that it can continue.
To improve your code:
var refreshObs = $scope.$createObservableFunction('refresh');
refreshObs
.flatMapLatest(function (x, idx, obs) {
// get the results.
// in here might throw an exception
// should return an Observable of the results
})
.catch(function(e) {
// do something with the error
return Rx.Observable.empty(); // replace the error with nothing
})
.subscribe(function (x) {
// on next
});
Notice also that I removed onError and onComplete handlers since there isn't anything to do inside them.
Also take a look at more operators. For instance retry() can be used to automatically 'get results' again every time an error happens. See https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/retry.md
Use retry() in combination with do() in order to handle the error (do), and allow the subscriber to automatically resubscribe to the source observable (retry).
refreshObs
.flatMapLatest(function (x, idx, obs) {
// get the results.
// in here might throw an exception
// should return an Observable of the results
})
.do(function(){}, // noop for onNext
function(e) {
// do something with the error
})
.retry()
.subscribe(function (x) {
// on next
});
See a working example here: http://jsfiddle.net/staltz/9wd13gp9/9/
Related
I'm manipulating some angular services/functions via Chrome console. (I have to specifically do this for a task I'm working on).
What I want to do is wait for the AddBagIfLimitNotReached() function to execute and finish running. And only then access the variable this.option.Quantity.
angular.element(document.querySelector(".quantity-button")).controller()._registeredControls[1].Scope.AddBagIfLimitNotReached = async function(n) {
console.log("tthis", this)
if (this.HasReachedMaximumBaggageAllowance()) {
angular.element(document.querySelector(".quantity-button")).controller()._registeredControls[1].LuggageDrawersService.OpenLuggageLimitReachedDrawer();
return;
}
this.AddBag(n);
console.log("Quantity", this.option.Quantity);
};
With this function, I'm adding a product to my basket. And this.option.Quantity should console.log 1. But it actually consoles.log 0.
However, if I check the object itself, it shows 1.
So I think what is happening, is I'm console.logging my bag quantity, before the bag has actually finished being added to the basket.
For example, if I added a settimeout of 2 seconds, the correct bag value = 1 is console.logged.
angular.element(document.querySelector(".quantity-button")).controller()._registeredControls[1].Scope.AddBagIfLimitNotReached = async function(n) {
console.log("tthis", this)
if (this.HasReachedMaximumBaggageAllowance()) {
angular.element(document.querySelector(".quantity-button")).controller()._registeredControls[1].LuggageDrawersService.OpenLuggageLimitReachedDrawer();
return;
}
this.AddBag(n);
// Returns 1
setTimeout(function(){ console.log("Quantity", this.option.Quantity); }, 2000);
};
Is there a better way I can achieve this, without using settimeout? I have tried async/await/promises, but I still can't seem to find a way to wait for the function to finish loading.
Async/await returns an error - it doesn't like the function this.HasReachedMaximumBaggageAllowance() and throws an error stating this.HasReachedMaximumBaggageAllowance is not a function.
Any tips/ideas would be much appreciated.
I found a solution, I'm using $watch, to watch a key/value, in the this object. And this seems to work:
angular.element(document.querySelector(".quantity-button.plus-button")).controller()._registeredControls[1].Scope.AddBagIfLimitNotReached = function(n) {
let bagCount = this.option.Quantity;
console.log("bagCount", bagCount);
if (this.HasReachedMaximumBaggageAllowance()) {
angular.element(document.querySelector(".quantity-button.plus-button")).controller()._registeredControls[1].LuggageDrawersService.OpenLuggageLimitReachedDrawer();
return;
};
this.AddBag(n);
this.$watch("this.option.Quantity", function (newValue) {
console.log(`Value of foo changed ${newValue}`);
if (newValue > 0) {
document.querySelector(`.luggage-tile-weight-${n.Weight} .tile-title .tick-box`).classList.add("green-tick");
displayGreenTickNoBagSelected();
};
if (newValue === 0) {
document.querySelector(`.luggage-tile-weight-${n.Weight} .tile-title .tick-box`).classList.remove("green-tick");
displayGreenTickNoBagSelected();
};
});
};
Angular js function updating some record. After updating record i am calling search method to show data on view.
But record does not updated before that search method call that does not get data so show null on view.
I have separate button for search on its ng-click this search method call. After some second if i click that button it shows data on view.
my code is,
vm.Update = function (value)
{
var test = value;
searchCriteria = {
From: vm.From,
To: vm.To,
Region: vm.Region,
City: vm.SelectedCity
}
surveyService.UpdateVisit(searchCriteria,value).then(function (d) {
var Confrm = JSON.parse(d.data.data);
if (d.data.status) {
toastr.success(Updated, {
autoDismiss: false
});
}
else {
toastr.error(errorMsg);
}
});
vm.searchVisit(0);
}
This searchvisit call and service unable to update data in database so i do not get any record on view. When i call this searchvisit method from separate button for searching it shows record with updated data.
Hopes for your suggestions how to pause execution before calling searchvisit method or any alternative that it gets any response than move execution control to searchvisit method.
Thanks
This is due to the asynchronous nature in JS.
From your code, surveyService.UpdateVisit(searchCriteria,value) returns a promise. Thus, when vm.searchVisit(0); is called, surveyService.UpdateVisit(searchCriteria,value) has not been resolved yet, meaning updating is still in progress and have not been completed. There for vm.searchVisit(0); shows records that are not updated.
If your second function is dependent on the values of the first function call, please add it as shown below inside the success callback.
surveyService.UpdateVisit(searchCriteria,value).then(function (d) {
var Confrm = JSON.parse(d.data.data);
if (d.data.status) {
toastr.success(Updated, {
autoDismiss: false
});
}
else {
toastr.error(errorMsg);
}
//Add this here.
vm.searchVisit(0);
});
I'm adding elements to an array using a service, which successfully adds the elements. I can see the data populated with a console.log
I can't however access the element.
this.routingService.getNode(startNode).subscribe(node => {
node.forEach(node => {
this.openSet.push(new MapNode(node.id, node.lon, node.lat, this.routingService.getNodeNeighbours(node.id)));
});
});
console.log(this.openSet); // this prints out the below screenshot
However, when I use something like:
console.log(this.openSet[0]);
I get output of 'undefined'. I'm not sure if I'm being really thick right now in how I'm accessing it or not...
Any ideas?
subscribe works asynchron, so console.log() will log before the forEach loop runs. It's the same async behaviour as in this little piece of code
let foo = [];
setTimeout(() => {
foo.push(1);
foo.push(2);
}, 500);
console.log(foo); // logs []
Please see the duplicate post for options on how to work with asynchronity.
How to return the response from an asynchronous call?
When I click the export button, it makes a REST call to our endpoint then few seconds after, I receive the response then I also render the table. Unfortunately, I read that every call is asynchronous which means my expect will be executed even if table hasn't been rendered yet. The expect I wrote checks if the string is on the table but it's failing since it's not there yet. What is the proper approach to this?
it('should generate global user report', function() {
element(by.css('button#exportButton')).click();
expect(element(by.css("th[name*=Date]")).getText()).
toEqual('Date');
})
The error on the console is
NoSuchElementError: No element found using locator: By.cssSelector("th[name*=Date]")
I noticed that the table hasn't been rendered yet that's why it's failing.
Protractor 1.7 introduced a feature called "Expected Conditions", that can be applied here.
Wait for element to become visible:
var EC = protractor.ExpectedConditions;
var elm = element(by.css("th[name*=Date]"));
browser.wait(EC.visibilityOf(elm), 5000);
expect(elm.getText()).toEqual('Date');
I had problem waiting for a dynamic element to appear. Have the driver wait for it to either be present or displayed. The number at the end is the timeout.
element(by.css('button#exportButton')).click();
var header = element(by.css("th[name*=Date]"));
browser.driver.wait(function() {
return header.isPresent();
}, 1000);
expect(header.getText()).toEqual('Date');
I had to wait until it was present AND displayed before the test was fully stable. You can do that like this:
var header = element(by.css("th[name*=Date]"));
browser.driver.wait(function() {
return header.isPresent().then(function(present) {
if (present) {
return header.isDisplayed().then(function(visible) {
return visible;
});
} else {
return false;
}
});
}, 1000);
Im working on an extjs application. We're have a page that is for looking at a particular instance of an object and viewing and editing it's fields.
We're using refs to get hold of bits of view in the controller.
This was working fine, but I've been sharding the controller into smaller pieces to make it more managable and realised that we are relying on a race condition in our code.
The logic is as follows:
Initialise the controller
parse the url to extract the id of the object
put in a call to load the model with the given view.
in the load callback call the controller load method...
The controller load method creates some stores which fire off other requests for bits of information using this id. It then uses some of the refs to get hold of the view and then reconfigures them to use the stores when they load.
If you try and call the controller load method immediately (not in the callback) then it will fail - the ref methods return undefined.
Presumably this is because the view doesnt exist... However we aren't checking for that - we're just relying on the view being loaded by the time the server responds which seems like a recipe for disaster.
So how can we avoid this and be sure that a view is loaded before trying to use it.
I haven't tried rewriting the logic here yet but it looks like the afterrender event probably does what I want.
It seems like waiting for both the return of the store load and afterrender events should produce the correct result.
A nice little abstraction here might be something like this:
yourNamespace.createWaitRunner = function (completionCallback) {
var callback = completionCallback;
var completionRecord = [];
var elements = 0;
function maybeFinish() {
var done = completionRecord.every(function (element) {
return element === true
});
if (done)
completionCallback();
}
return {
getNotifier: function (func) {
func = func || function (){};
var index = elements++;
completionRecord[index] = false;
return function () {
func(arguments);
completionRecord[index] = true;
maybeFinish();
}
}
}
};
You'd use it like this:
//during init
//pass in the function to call when others are done
this.waiter = yourNamespace.createWaitRunner(controller.load);
//in controller
this.control({
'SomeView': {
afterrender: this.waiter.getNotifier
}
});
//when loading record(s)
Ext.ModelManager.getModel('SomeModel').load(id, {
success: this.waiter.getNotifier(function (record, request) {
//do some extra stuff if needs be
me.setRecord(record);
})
});
I haven't actually tried this out yet so it might not be 100% but I think the idea is sound