Angular Code Inside Subscribe() How to Run Synchronouly - javascript

_subscriptions.push(
// Update list on OG selection
ogViewModel.current$.subscribe(
(group) => {
this.groupUuid = group.ogUuid;
this.groupId = group.id;
this.getDevices();
this.getSubOgs();
this.updateGroup(group);
},
),
I have a block of code inside subscribe. However, it seems that they are not executed in order. this.getSubOgs() is executed before this.getDevices(). Both are HTTP calls that returns an observable. How do I make sure this.getDevices() is executed first?
Click to See Codes

this is basically #MishaMashina's suggestion
_subscriptions.push(ogViewModel.current$.subscribe(
(group) => {
this.groupUuid = group.ogUuid;
this.groupId = group.id;
this.getDevices();
this.updateGroup(group);
},
),
both getDevices() and getSubOgs() remain seperate methods
in
public getDevices() { this.myApiClient.getDevices().Subscribe(
let onlyThisTime: boolean = true;
(next) => doStuff
(error) => doErrorStuff
() => {if(onlyThisTime){this.getSubOgs();}}
)}

Related

Assigning values received from api calls for each element in the loop iteration using Observables

I have a foreach loop through which I am iterating and want to call functions which would in turn make async api calls and return value which can be rendered in the html.
The 1st function call getCurrentValue() would return currentTemperatureRef which I finally want to assign receivedCurrentValue and render in html
The 2nd function call getDesiredValue1() would return desiredValueToBeReturned which I finally want to assign receivedDesiredValue1 and render in html
ts
myObj = { droppedItem: [] };
elements = [
{ elementId: 1, elementName: "name1" },
{ elementId: 2, elementName: "name2" },
{ elementId: 3, elementName: "name3" },
{ elementId: 4, elementName: "name4" }
];
this.elements.forEach(element => {
let receivedCurrentValue = this.getCurrentValue(element.name);
let receivedDesiredValue1 = this.getDesiredValue1(element.id);
this.myObj.droppedItem.push(receivedCurrentValue)
this.myObj.droppedItem.push(receivedDesiredValue1)
}
getCurrentValue(eleName){
//1st api(async api call)
var ref = this.helperService.getPointIdbyTags(this.existingObj, ['current',
'temp'], eleName)[0];
//2nd api(async api call which expects ref value from above api call)
this.siteService.getHisPointData(ref, 'current')
.pipe(
map(this.helperService.stripHaystackTypeMapping),
)
.subscribe(({ rows }) => {
if (rows.length > 0) {
this.currentTemperatureRef = rows[0].val;
}
});
}
getDesiredValue1(eleId){
//1st async api call
this.siteService.getScheduleParamsByRoomRef('temp and air and desired and
heating', eleId)
.subscribe(function (a) {
let row = a;
let pointId = this.helperService.stripHaystackTypeMapping(row['id']).split(' ')[0];
//2nd async api call expecting pointId from above api call
this.siteService.getHisPointData(pointId, 'current')
.subscribe(function (a) {
let rows = a.rows,
if (rows.length > 0) {
let desiredValueToBeReturned = rows[0].val;
)
}
)
}
}
html
<div *ngFor="let existingItem of myObj?.droppedItem">
<span>{{existingItem.receivedValue}}</span>
<span>{{existingItem.receivedDesiredValue1}}</span>
<span>{{existingItem.receivedDesiredValue2}}</span>
</div>
Update
when I try to
getCurrentValue(eleName){
let roomObj = this.getRoomObj(eleName);
let equipRef = roomObj.map(equip => equip.entities.filter(entity => entity.entities.length > 0)[0])[0];
return this.helperService.getPointIdbyTags(this.buildings, ['current',
'temp'], equipRef.referenceIDs.room)[0].pipe(switchMap((res:any)=>{
//we don't want "res" else the response of
return this.siteService.getHisPointData(res, 'current')
.pipe(
map(this.helperService.stripHaystackTypeMapping),
)
}));
}
I get an error on line => return this.helperService.getPointIdbyTags(this.buildings, ['current',
'temp'], equipRef.referenceIDs.room)[0].pipe(switchMap(
ERROR TypeError: this.helperService.getPointIdbyTags(...)[0].pipe is
not a function
I don't undestand so much the question, but you need understand some about forkJoin and switchMap. SwitchMap it's usefull when you need make two calls one depending the response of the another. The construction becomes like
callOne.pipe(
switchMap(resposeOfCallOne=>{
return callTwo(responseOfCallOne)
})
If subscribe you received the response of callTwo
forkJoin get an array of calls and return the result in an array
forkJoin([callOne,callTwo])
if subscribe you received an array: res[0] has the response of callOne and res[1] the response of callTwo
Well, First convert your functions getCurrentValue and getDesiredValue1 to return observables
getCurrentValue(eleName){
return this.helperService.getPointIdbyTags(this.existingObj, ['current',
'temp'], eleName)[0].pipe(switchMap((res:any)=>{
//we don't want "res" else the response of
return this.siteService.getHisPointData(ref, 'current')
.pipe(
map(this.helperService.stripHaystackTypeMapping),
)
};
}
getDesiredValue1(eleId){
return this.siteService.getScheduleParamsByRoomRef('temp and air and desired and
heating', eleId).pipe(
switchMap((a:any)=>{
let row = a;
let pointId = this.helperService.stripHaystackTypeMapping(row['id']).split(' ')[0];
return this.siteService.getHisPointData(pointId, 'current')
}))
Well, when we has an element we want create the two calls, we are going to use forkjoin
We want to make, forEach element create two calls, so we can make
this.elements.forEach(element => {
forkJoin([this.getCurrentValue(element.name),this.getDesiredValue1(element.id)])
.subscribe(([current,desired])=>{
element.current=current;
element.desired=desired;
})
})
I user in subscribe ([current,desired]) but we can use res and use element.current=res[0],element.desired=res[1]
If we want, we can even make only one subscription -now we has so many subscriptions as element we have-
arrayOfCalls=[]
this.elements.forEach(element => {
arrayOfCalls.push(
forkJoin([this.getCurrentValue(element.name),this.getDesiredValue1(element.id)])
)
}
//then subscribe
arrayOfCalls.subscribe((fullRes:any[])=>{
fullRes.map((res,index)=>{
this.elements[index].currentValue=res[0]
this.elements[index].desiredValue=res[1]
})
})

How to chain suscriptions

Hi I am trying to chain following subscriptions.
changeBranch() {
const bottomSheetRef: MatBottomSheetRef = this.bottomSheet.open(CCSBranchComponent, {
data: this.branches
});
this.subscription.add(bottomSheetRef.instance.change.subscribe((branch: Branch) => {
this.branchInfo = `Description : ${branch.author}\nAuthor : ${branch.id}\nCreated date :${branch.created}`;
this.blockpointBranchForm.get('branch').setValue(branch.id);
}));
this.subscription.add(bottomSheetRef.afterDismissed().subscribe(() => {
this.returnSelectedBranch.emit(this.blockpointBranchForm.get('branch').value);
}));
}
Here if bottomSheetRef.instance.change.subscribe is called before the sheet loads, it throws undefined. So i am trying the implement something that looks like this
this.subscription.add(this.bottomSheet.open(CCSBranchComponent, {
data: this.branches
}).instance.change.subscribe((branch: Branch) => {
this.branchInfo = `Description : ${branch.author}\nAuthor : ${branch.id}\nCreated date :${branch.created}`;
this.blockpointBranchForm.get('branch').setValue(branch.id);
}).afterDismissed().subscribe(() => {
this.returnSelectedBranch.emit(this.blockpointBranchForm.get('branch').value);
}));
Here the second subscribe is called on the subscription returns by first. How do I access the observable in the chain?
I guess what you want is to chain the the actions what are done when subscribing.
You can achieve this by
bottemsheetRef.instance.change.pipe(
switchmap(resultFromChange => bottomSheetRef.afterDismissed
).subsbribe(resultFromAfterDismissed => {// do whatever you like})

How to clear fields after callback from axios?

I have modal component with form. I want to inform fields of this form that form data was successfully sent to database and clear its fields.
Component code:
//ItemModal.js
addItem(e) {
e.preventDefault();
const item = {
id: this.props.itemsStore.length + 1,
image: this.fileInput.files[0] || 'http://via.placeholder.com/350x150',
tags: this.tagInput.value,
place: this.placeInput.value,
details: this.detailsInput.value
}
console.log('addded', item);
this.props.onAddItem(item);
this.fileInput.value = '';
this.tagInput.value = '';
this.placeInput.value = '';
this.detailsInput.value = '';
this.setState({
filled: {
...this.state.filled,
place: false,
tags: false
},
loadingText: 'Loading...'
});
}
...
render() {
return (
<div className="text-center" >
<div className={"text-center form-notification " + ((this.state.loadingText) ? 'form-notification__active' : '' )}>
{(this.state.loadingText) ? ((this.props.loadingState === true) ? 'Item added' : this.state.loadingText) : '' }
</div>
)
}
action.js
export function onAddItem(item) {
axios.post('http://localhost:3001/api/items/', item )
.then(res => {
dispatch({type:"ADD_ITEM", item});
dispatch({type:"ITEM_LOADED", status: true});
})
}
helper.js
else if (action.type === 'ITEM_LOADED') {
const status = action.status;
return {
...state,
isItemLoaded: status
}
}
Currently I have few issues with my code:
1. field are clearing right after click, but they should clear after changing state of loadingState. I tried to check it in separate function on in componentWillReceiveProps whether state is changed and it worked, but I faces another problem, that after closing this modal there were errors, that such fields doesn't exist.
2. loadingText should become '' (empty) after few seconds. Tried same approach with separate function and componentWillReceiveProps as at first issue.
In constructor keep a copy of your initial state in a const as follows:
const stateCopy = Object.create(this.state);
When your ajax request completes, in the sucess callback you can reset the state with this copy as follows:
this.setStae({
...stateCopy
});
One of the few ways to achieve this is to use async await which will resolve the promises and then return the value after that you can clear the values
1st approach using the async await
Here is the example
handleSubmit = async event => {
event.preventDefault();
// Promise is resolved and value is inside of the response const.
const response = await API.delete(`users/${this.state.id}`);
//dispatch your reducers
};
Now in your react component call it
PostData() {
const res = await handleSubmit();
//empty your model and values
}
Second approach is to use the timer to check the value is changed or not
for this we need one variable add this to the service
let timerFinished=false;
one function to check it is changed or not
CheckTimers = () => {
setTimeout(() => {
if (timerFinished) {
//empty your modal and clear the values
} else {
this.CheckTimers();
}
}, 200);
}
on your add item change this variable value
export function onAddItem(item) {
axios.post('http://localhost:3001/api/items/', item)
.then(res => {
timerFinished = true;
dispatch({
type: "ADD_ITEM",
item
});
dispatch({
type: "ITEM_LOADED",
status: true
});
})
}
and here is how we need to call it.
PostData = (items) => {
timerFinished = false;
onAddItem(items);
this.CheckTimers();
}
If you check this what we done is continuously checking the variable change and emptied only once its done.
One thing you need to handle is to when axios failed to post the data you need to change the variable value to something and handle it, you can do it using the different values 'error','failed','success' to the timerFinished variable.

Protractor and click item for loader

I m having some trouble testing the apparition of a loader when clicking a button.
I have the following three test :
it('#exigence1 - display a failing message on button fail ', () => {
page.navigateTo();
page.getButtonFail().click();
expect(page.getErrorMessage()).toBeTruthy();
});
it('#exigence2 - display a loader on waiting list ', () => {
page.navigateTo();
page.getButtonLoad().click();
expect(page.getLoader()).toBeTruthy();
});
it('#exigence3 - display a list of items on message ', () => {
page.navigateTo();
page.getButtonLoad().click();
expect(page.getPokeList()).toBeTruthy();
});
When I click the getButtonLoad, I display a loader during the remote fetch. I need to test that this loader appears on the page, but my previous second test doesn't pass.
Here's my implementation :
buttonClick() {
this.showError = false;
this.displayLoader = true;
this.http.get('http://pokeapi.co/api/v2/pokemon/')
.map(res => res.json())
.subscribe(res => {
this.pokemons = res.results;
this.displayLoader = false;
});
}
With my page definition :
export class ProtractorDemonstrationPage {
navigateTo() {
return browser.get('/');
}
getPokeList() {
return element(by.css('.poke-list')).getText();
}
getButtonLoad() {
return element(by.css('.btn-primary'));
}
getButtonFail() {
return element(by.css('.btn-warn'));
}
getErrorMessage() {
return element(by.css('.panel-danger')).getText();
}
getLoader() {
return element(by.css('.loader')).getText();
}
}
I don't know at all how to make a test that validate this case.
EDIT : What I see there is that the click, when clicked, is waiting for the function to finish its stuff before getting to the next instruction. Here's my problem. I need to click, and don't wait for the async stuff and directly check that my loader is there.
EDIT 2 : Here's the code of the function :
load() {
this.showError = false;
this.displayLoader = true;
this.http.get('http://pokeapi.co/api/v2/pokemon/')
.map(res => res.json())
.subscribe(res => {
this.pokemons = res.results;
this.displayLoader = false;
});
}
Does
The issue is with page.getLoader() and your expect statement. With the page.getLoader() call you are returning the text of that element but in your expect statement you are expecting this function to return a boolean value.
You should change your page.getLoader() to return:
return element(by.css('.loader')).isDisplayed();
or use keep using .getText() but change your expect statement to:
expect(page.getLoader()).toBe('This string');

How can I stop multiple calls to a function inside an interval?

How could I ensure that one one call of a function in an interval can run at once?
My code looks like this:
var promisePutTestQuestion;
onEnter: ['$interval', 'questionService',
($interval, qus: IQuestionService) => {
promisePutTestQuestion = $interval(() => {
qus.putTestQuestionResponses();
}, 5 * 1000);
}],
onExit: ['$interval',
($interval) => {
$interval.cancel(promisePutTestQuestion);
}],
The problem I am having is if the putTestQuestionResponse (which returns a promise) is slow then I have multiple putTestQuestionResponse function calls one after the other.
Would appreciate if anyone has any ideas on this. Thanks
In javascript it's safe to use state variable for this purpose. Whenever qus.putTestQuestionResponses operation takes a lot of time next call will be skipped. So just maintain right state of processing variable, and if it's true quit the interval callback without running task again.
var promisePutTestQuestion;
let processing = false;
onEnter: ['$interval', 'questionService',
($interval, qus: IQuestionService) => {
promisePutTestQuestion = $interval(() => {
if (processing)
return;
processing = true;
qus.putTestQuestionResponses()
.then(() => processing = false)
}, 5 * 1000);
}],
onExit: ['$interval', ($interval) => {
$interval.cancel(promisePutTestQuestion);
}]
Not sure what promise framework you're using, but () => processing = false should be either in finally() for $q I guess, and in the then that should be after catch to ensure it is executed in any case.
So one of these:
qus.putTestQuestionResponses().finally(() => processing = false)
or
qus.putTestQuestionResponses().catch(...).then(() => processing = false)`
or even
qus.putTestQuestionResponses().then(() => processing = false).catch(() => processing = false)

Categories

Resources