Return observable from nested observable - angularjs

I am trying to return a boolean observable from a response that I get from an observable that is inside a response from a parent observable. But the child observable will not always run depending on the res from the parent observable.
I know to make this work I have to use .map and I can return the observable in the subscribe but after that I am stumped.
the scenario is that I do an auth check if that passes then do the api call if it fails return false. If the api call fails return false and if it succeeds return true.
getEvents(): Observable<boolean> {
this.authSrvc.authCheck().map((res: boolean) => {
if (res) {
this.eventsSrvc.getEvents(this.pageNum, this.pageSize, this.searchText).timeout(15000).map((data: Response) => data.json()).subscribe((res:any)=>
{
if(res.value.length === 0)
{
Observable.of(false);
}
else
{
this.eventsList = this.eventsList.concat(data);
this.storage.ready().then(() => {
this.storage.set('events', this.eventsList)
})
Observable.of(true);
}
},(err:any)=>
{
this.helperSrvc.errorMessage(err);
return Observable.of(false);
})
}
else {
this.helperSrvc.authFailed();
this.authSrvc.logout();
this.pushSrvc.unRegisterPush();
this.calendarSrvc.clearEvents();
this.locationSrvc.clearGeofences();
this.navCtrl.setRoot(AuthPage);
return Observable.of(false);
//
}
})
}
I either cant get the response or I get told that the function that calls this doesnt have .subscribe() available.

I think you need to use flatMap, I have changed your code below.
getEvents(): Observable<boolean> {
return this.authSrvc.authCheck().flatMap((res: boolean) => {
if (res) {
return this.eventsSrvc.getEvents(this.pageNum, this.pageSize, this.searchText)
.timeout(15000)
.map((data: Response) => data.json())
.flatMap((res: any) => {
if (res.value.length === 0) {
return Observable.of(false);
}
else {
this.eventsList = this.eventsList.concat(data);
this.storage.ready().then(() => {
this.storage.set('events', this.eventsList);
});
return Observable.of(true);
}
});
}
else {
return Observable.of(false);
//
}
})
}
EDIT: I removed your error handler, you need to pass it when you subscribe to getEvents.
getEvents().subscribe(
(res:boolen) => {},
(err:any)=>{
this.helperSrvc.errorMessage(err);
}
);

Related

Local storage handling in react in another class

I had a component that each time something was added to state was added to local storage as well. It was deleted from local storage on componentWillUnmnout. I was told to prepare an indirect abstract layer for local storage handling in order to follow single responsibility principle.
I am confused how this could be done, can someone give an example of such layer, class?
componentWillUnmount() {
localStorage.removeItem('currentUser');
}
static getDerivedStateFromProps(nextProps) {
const currUser = JSON.parse(
localStorage.getItem('currentUser')
);
if (
currUser && nextProps.users.some(
(user) => user.id === currUser.id
)
) {
return {
user: currUser,
};
}
return null;
}
const onSelect = (
user
) => {
this.setState({
user,
});
localStorage.setItem('currentUser', JSON.stringify(user));
}
private onRemove = () => {
this.setState({
user: null,
});
localStorage.removeItem('currentUser');
}
Applying single responsibility principle here might be over-programming, since Javascripts is not OOP. But if you need, there are some concerns with using localStorage directly that can be separated:
Your component doesn't need to know where you store persistent data. In this case, it doesn't need to know about the usage of localStorage.
Your component doesn't need to know how you store the data. In this case, it doesn't need to handle JSON.stringify to pass to localStorage, and JSON.parse to retrieve.
With those ideas, an interface for localStorage can be implemented like so
const Storage = {
isReady: function() {
return !!window && !!window.localStorage;
},
setCurrentUser: function(user) {
if (!this.isReady()) throw new Error("Cannot find localStorage");
localStorage.setItem('currentUser', JSON.stringify(user));
return true;
},
getCurrentUser: function() {
if (!this.isReady()) throw new Error("Cannot find localStorage");
if (localStorage.hasOwnProperty('currentUser'))
{
return JSON.parse(localStorage.getItem('currentUser'));
}
return null;
},
removeCurrentUser: function() {
if (!this.isReady()) throw new Error("Cannot find localStorage");
localStorage.removeItem('currentUser');
return true;
}
}
By importing Storage object, you can rewrite your component:
componentWillUnmount() {
Storage.removeCurrentUser();
}
static getDerivedStateFromProps(nextProps) {
const currUser = Storage.getCurrentUser();
if (
currUser && nextProps.users.some(
(user) => user.id === currUser.id
)
) {
return {
user: currUser,
};
}
return null;
}
const onSelect = (
user
) => {
this.setState({
user,
});
Storage.setCurrentUser(user);
}
private onRemove = () => {
this.setState({
user: null,
});
Storage.removeCurrentUser();
}

how to get the result from recursive promises in a redux action

I've searched the net, and I can't find out a solution. My final goal is to pull all the data from a dynamodb table. The problem is when a table is bigger than 1MB, in the response I'll get one chunk of data and a LastEvaluatedKey parameter (which provides the index I can use in the next call to get the next chunk). The scan operation is documented here if needed.
I'm using reactjs, redux and redux-thunk in my app.
I have used promises moderately in the single or chained formats, but this one is more challenging that I could resolve so far. What puzzles me is the fact that the new calls can not be made without receiving the previous response, so the calls can not be done simultaneously in my opinion. In another hand since the scan operation is a promise (as far as I understand) if I try to return a promise from my own method the action does not receive the results.
I'm very confused and I really like to understand how I can get this to work.
action:
function getDynamodbTableRecords(tableName) {
return dispatch => {
dispatch(request());
var recordsSet = [];
var data = myAwsService.getTableRecords(tableName, null) || {Items:[]};
if (data.Items.length > 0){
data.Items.map(record => {
recordsSet.push(record);
});
dispatch(success(recordsSet));
} else {
dispatch(failure("No Records Found!"));
}
};
function request() { return { type: DATA_LOADING, selectedTable: tableName } }
function success(tableRecords) { return { type: DATA_LOAD_SUCCESS, tableRecords } }
function failure(error) { return { type: DATA_LOAD_FAILED, errors: error } }
}
myAwsService:
function getTableRecords(tableName, lastEvaluatedKey = null) {
getRecordsBatch(tableName, lastEvaluatedKey)
.then(
data => {
if (data.LastEvaluatedKey) {
return getTableRecords(tableName, data.LastEvaluatedKey)
.then(
nextData => {
data.Items = data.Items.concat(nextData.Items);
}
)
}
return data;
}
)
}
function getRecordsBatch(tableName, lastEvaluatedKey = null) {
var awsDynamodb = new DynamoDB();
let params = { TableName: tableName };
if (lastEvaluatedKey) {
params['ExclusiveStartKey'] = lastEvaluatedKey;
}
return new Promise((resolve, reject) => {
awsDynamodb.scan(params, function(err, data) {
if (err) {
reject(err);
}
return resolve(data);
});
});
}
Not sure if your recursive promise is working but I'd do it like this:
function getTableRecords(
tableName,
lastEvaluatedKey = null,
result = { Items: [] }
) {
return getRecordsBatch(tableName, lastEvaluatedKey).then(
data => {
if (data.LastEvaluatedKey) {
return getTableRecords(
tableName,
data.LastEvaluatedKey,
{
...data,
Items: result.Items.concat(data.Items),
}
);
}
return {
...data,
Items: result.Items.concat(data.Items),
};
}
);
}
The action should also dispatch the data.Items and not the promise that getTabelRecords returns and you probably want to dispatch failure action if something goes wrong:
function getDynamodbTableRecords(tableName) {
return async dispatch => {
dispatch(request());
//you probably want the data, not a promise of data
try {
var data = await myAwsService.getTableRecords(
tableName,
null
);
if (data.Items.length > 0) {
//no reason to have the temporary recordSet variable
dispatch(success(data.Items.map(record => record)));
} else {
dispatch(failure('No Records Found!'));
}
} catch (e) {
dispatch(failure(e.message));
}
};
function request() {
return { type: DATA_LOADING, selectedTable: tableName };
}
function success(tableRecords) {
return { type: DATA_LOAD_SUCCESS, tableRecords };
}
function failure(error) {
return { type: DATA_LOAD_FAILED, errors: error };
}
}

Reusing the result of an http call

I have the following use case:
Two visual grids are using two methods to load the data to display. These methods are automatically called by the grids and this part cannot be changed, it's by design:
loadDataForGrid1 = (params: any): any => {
return this.httpService.getData().then((response) => {
return response.dataGrid1;
}, (err) => {
});
}
loadDataForGrid2 = (params: any): any => {
return this.httpService.getData().then((response) => {
return response.dataGrid2;
}, (err) => {
});
}
Everything is working fine but my problem is performance. Since the getData method does an http request that is quite huge, calling it twice like Im doing right now is not acceptable. It there a way to solve this problem by doing only one call? Like caching the data so that they are reusable by the second call?
Im using typescript and angularjs
Edit:
Something like this would not work since the result would not be available when the grids load the data:
result: any;
// called at the beginning, for example contructor
loadData = (params: any): any => {
return this.httpService.getData().then(result => {
this.result = result;
});
}
loadDataForGrid1 = (params: any): any => {
return this.result.gridGrid1;
}
loadDataForGrid2 = (params: any): any => {
return this.result.gridGrid2;
}}
Using the answer suggested by #georgeawg generates the following javascript (which does 2 calls)
this.loadDataForGrid1 = function (params) {
_this.promiseCache = _this.promiseCache || _this.httpService.getData();
return _this.promiseCache.then(function (response) {
return response.gridGrid1;
}, function (err) {
});
};
this.loadDataForGrid2 = function (params) {
_this.promiseCache = _this.promiseCache || _this.httpService.getData();
return _this.promiseCache.then(function (response) {
return response.gridGrid2;
}, function (err) {
});
};
You can always store the the data array in a variable on the page for SPA. If you want to use the data over different pages, you can use localStorage to 'cache' the data on the client-side.
localStorage.set("mydata", response.dataGrid1);
localStorage.get("mydata");
FYI, i does not seem you are using typescript, but rather native javascript :-)
--
Why don't you do something like this, or am i missing something?
$scope.gridData = {};
loadDataForGrid1 = (params: any): any => {
return this.httpService.getData.then((response) => {
$scope.gridData = response;
}, (err) => {
}).finally(function(){
console.log($scope.gridData.gridData1);
console.log($scope.gridData.gridData2);
});
}
What you can do is store the returned variable into a service variable and then do a check if it already exists.
dataGrid;
loadDataForGrid1 = (params: any): any => {
if(!this.dataGrid) {
return this.httpService.getData.then((response) => {
this.dataGrid = response;
return this.dataGrid.dataGrid1;
}, (err) => {
});
}
return this.dataGrid.dataGrid1;
}
loadDataForGrid2 = (params: any): any => {
if(!this.dataGrid) {
return this.httpService.getData().then((response) => {
this.dataGrid = response;
return this.dataGrid.dataGrid2;
}, (err) => {
});
}
return this.dataGrid.dataGrid2;
}
Something like this should work. Every time you call loadDataForGrid1 or loadDataForGrid2 you will first check if the data is already there - therefore you make an API call only once.
The solution is to cache the promise and re-use it:
var promiseCache;
this.loadDataForGrid1 = (params) => {
promiseCache = promiseCache || this.httpService.getData();
return promiseCache.then(result => {
return result.gridGrid1;
});
}
this.loadDataForGrid2 = (params) => {
promiseCache = promiseCache || this.httpService.getData();
return promiseCache.then(result => {
return result.gridGrid2;
});
}
Since the service immediately returns a promise, it avoids the race condition where the
second XHR is started before the first XHR returns data from the server.
You mean that would be a javascript solution? But how to do it with typescript then?
JavaScript supports private variables.1
function MyClass() {
var myPrivateVar = 3;
this.doSomething = function() {
return myPrivateVar++;
}
}
In TypeScript this would be expressed like so:
class MyClass {
doSomething: () => number;
constructor() {
var myPrivateVar = 3;
this.doSomething = function () {
return myPrivateVar++;
}
}
}
So, after many hours I came to the following solution. It's a bit a hack but it works.
In the initialization (constructor or so) Im loading the data:
this.httpService.getData().then((response) => {
this.data1 = response.dataGrid1;
this.data2 = response.dataGrid2;
// other properties here...
this.isReady= true;
}, (err) => {
});
then I wrote an ugly wait method
wait(): void {
if (this.isReady) {
return;
} else {
setTimeout(this.wait, 250);
}
}
Finally, my two methods look like this
loadDataForGrid1 = (params: any): any => {
this.wait();
return this.$q.resolve(this.data1);
}
loadDataForGrid2 = (params: any): any => {
this.wait();
return this.$q.resolve(this.data2);
}

React Native function not triggering

So, I have this component,
{ this.props.isConfirmModalOpen && shiftInvite && <ConfirmApplicationPopUp
memberPhoto={props.memberProfile && props.memberProfile.photo}
venueLogo={getOr('noop', 'account.logo', shiftInvite)}
isOpen={props.isConfirmModalOpen}
shift={shiftInvite}
isOnboarding={isOnboardingMember}
onClose={props.onToggleConfirmPopUp}
onConfirm={this.handleConfirmApplication}
checkValues={props.confirmationCheckValues}
onUpdateCheckValues={props.onUpdateConfirmationCheckValues}
isHomeComponent
/> }
As you can see, I pass on onConfirm the handleConfirmApplication function, which is a check for some stuff and has to run a function in the try block, , here's the function
handleConfirmApplication = async () => {
const checkVals =
get('shift.account.accountName', this.props) === ONBOARDING_ACCOUNT
? omit('payRate', this.props.confirmationCheckValues)
: this.props.confirmationCheckValues;
if (Object.values(checkVals).every(val => val)) {
this.props.onToggleConfirmPopUp();
this.props.onToggleLoadingApply();
try {
console.log('inTry1');
await this.handleShiftInviteDecision('ACCEPT');
console.log('inTry2');
} catch (e) {
Alert.alert('Error', parseError(e));
} finally {
this.props.onToggleLoadingApply();
console.log('inFinally');
}
} else {
Alert.alert('Error', 'Please confirm all shift requirements');
}
};
My problem is, for whatever reason, it doesn't run the handleShiftInviteDecision('ACCEPT) for whatever reason, i'm awaiting it, tried to put it in another function, call them both from another function ETC, the function does not run!
Here's the handleShiftInviteDecision function too
handleShiftInviteDecision = (decision: 'ACCEPT' | 'DECLINE') => async () => {
console.log('handleSIDecision1');
const [shiftInvite] = getOr([], 'shiftInvites', this.state.modals);
console.log('handleSIDecision2');
if (decision === 'ACCEPT') {
analytics.hit(new PageHit(`ShiftInviteModal-ACCEPT-${shiftInvite.id}`));
console.log('handleSIDecision3');
} else if (decision === 'DECLINE') {
analytics.hit(new PageHit(`ShiftInviteModal-DECLINE-${shiftInvite.id}`));
console.log('handleSIDecision4');
}
try {
console.log("thisSHouldRun")
this.setState({ isLoading: true, display: false });
await this.props.updateMyApplication(shiftInvite.id, decision);
console.log('handleSIDecision5');
} catch (e) {
Alert.alert('Error', parseError(e));
} finally {
this.setState({ isLoading: false, display: false });
}
};
Any ideeas on what I could do?
The function handleShiftInviteDecision is a function that returns an async function.
handleShiftInviteDecision = (decision: 'ACCEPT' | 'DECLINE') => async () => {
So, you would need to call the async function it returns to invoke it:
try {
console.log('inTry1');
await this.handleShiftInviteDecision('ACCEPT')(); // <--- here
console.log('inTry2');
} catch (e) {

Execute method after getting response from $http.get

I want to add a profile if it doesn't exist, otherwise, I will only do an update:
profileExists(id) {
return this.$http.get('/profiles/' + id).then(response => {
return response.data;
});
}
submitProfile(profile) {
if (!this.profileExists(profile.id)) {
this.addProfile(profile);
} else {
this.updateProfile(profile);
}
}
addProfile(profile) {
return this.$http.post('/profiles', profile)
.then(res => res.data)
.catch(this.$http.fallback);
}
updateProfile(profile) {
return this.$http.put('/profiles/' + profile.id)
.then(res => res.data)
.catch(this.$http.fallback);
}
The problem with this code is that in the submitProfile method, this.addProfile(profile); is executed before the return statement of profileExists(id)... I have a hard time manipulating asynchronous code. I don't know how to execute the code after finishing all the profileExists(id) method.
And my second question is why do we put a return statement on this.$http.get or this.$http.put?
Thanks.
I think's you need to call your addProfile() in success callback from your profileExists()
Try this.
profileExists(id) {
return this.$http.get('/profiles/' + id).then(response => {
if(!response.data){
this.addProfile(profile);
}else{
this.updateProfile(profile);
}
});
}
Or
profileExists(id){
return this.$http.get('/profiles/' + id);
}
submitProfile(profile) {
this.profileExists(profile.id).then(response => {
if (!response.data) {
this.addProfile(profile);
} else {
this.updateProfile(profile);
}
})
}
By the time your code reaches the if clause, profileExists has not returned, so it evaluates to false. You can change your code to check in the callback function
submitProfile(profile) {
this.profileExists(profile.id)
.then(response => {
if(!response.data){
this.addProfile(profile);
} else {
this.updateProfile(profile);
}
})
}
You can do it this way:
submitProfile(profile) {
return this.profileExists(profile.id)
.then(exists => {
if(!exists){
return this.addProfile(profile);
} else {
return this.updateProfile(profile);
}
})
}
We put return before the actual call because we want to return the promise . So whomever is calling submitProfile can perform some action after this action is performed. Like this :
service.submitProfile(profile)
.then(result => console.log('submit success'));
Also they can catch errors wherever it happened in all the code above in single place.
service.submitProfile(profile)
.then(result => console.log('submit success'))
.catch(err => console.error('Failed to submit',err);
You need to set async http true. Try adding this line of code in your config.
$httpProvider.useApplyAsync(true);

Resources