How to I get data from an observable to use in a different function? - javascript

So, I happen to have a function as follows:
public getAssemblyTree(id: number) {
....
const request = from(fetch(targetUrl.toString(), { headers: { 'responseType': 'json' }, method: 'GET' }));
request.subscribe(
(response) => {
response.json().then(data => {
(this.parseAssemblyTree(data['flat_assembly_graph']));
}
)
}
)
public updateAssemblyTree(id: number) {
let res = this.getAssemblyTree(id)
this.asmTree.next(res);
}
So, I basically need the data once the observable completes to be returned by the function to be stored in the variable res in the second function(public updateAssemblyTree(id: number) ). Can you guys please let me know on how to do that? I'm very new when it comes to using observables so writing the code down would really help me out. Thank you guy, for your help in advance :)

Assuming that getAssemblyTree returns an observable or a promise, you can just subscribe to it as well. Thus:
this.getAssemblyTree(id).pipe(takeUntil(this.componentDestroyed$))
.subscribe((response) => {
if(response) {
//set your method here.
}
}).
the variable this.componentDestroyed is defined as private componentDestroyed$ = new Subject<void>();
Make sure to also discard of it in your component's ngOnDestroy as so:
this.componentDestroyed$.next();
this.componentDestroyed$.complete();
So what happens here is that your component will keep listening to and responding to changes in the getAssesmblyTree method as long as the component itself is not destroyed. This will only work though if the return type of the method is an Observable or a promise (if it is a promise just wrap it in a from).
Edit: May be overkill, but you could also look into using a store and this way you can subscribe to parts of the state. This way it pairs quite well with async pipes.

Related

how to have jest resolve promises in order different than promises were called?

I am using Axios to do requests to my server. I am aware of how to mock Axios and supply positive and negative results to my code under test. But today I ran into a case where my code calls two api end points and to test the logic properly, I need to be able to control the order that they are resolved in.
Without doing anything special, I jest will resolve the first call first and then resolve the second call. But I need to ensure that my code can handle the calls being resolved in the opposite order. I can't figure out how to get jest to resolve the second call before resolving the first call.
A simple example of the code under test would be:
function underTest() {
axios.request('get', url1).then(() => { do something });
axios.request('get', url2).then(() => { do something else });
}
I have to ensure that the code does the right thing when having "do something" and "do something else" run in both sequences.
#Bergi asked how I am mocking the Axios requests now, so here goes.
In my production code I use NTypewriter to create Typescript classes for each C# controller I have on the back end. It produces something like this:
export class LeagueService {
public static getManagementSummaryRoute = (leagueId: number) => `/api/League/${leagueId}/ManagementSummary`;
public static getManagementSummary(leagueId: number) : Promise<LeagueManagementSummary> {
return MakeRequest<LeagueManagementSummary>("get", this.getManagementSummaryRoute(leagueId), null);
}
where MakeRequest<> is:
import axios, { AxiosResponse, Method } from 'axios';
export async function MakeRequest<T>(httpMethod: Method, url: string,
payload: any | undefined = null): Promise<T>
{
const r = await axios.request({
method: httpMethod,
url,
data: payload
});
return r.data;
}
This lets me call out to the end point like this:
BoxLeagueService.getSession(this.leagueId, this.sessionId)
.then((newData: SessionBox) => {
box.updateData(newData);
})
.catch();
Ok, then in the unit test code, I have to import axios and mock it. Jest has great support for axios.
import axios from 'axios';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
then in a test, I do something like this:
it('constructor should request the league info with live layout When preview is false', () => {
// Arrange
mockedAxios.request.mockResolvedValueOnce({ data: leagueInfo });
mockedAxios.request.mockClear();
// Act
model = new BLInfoModel(leagueIdStr, false);
// Assert
expect(mockedAxios.request).toHaveBeenCalledWith({
method: 'get',
url: BoxLeagueService.infoPageRoute(leagueId, false),
data: null
});
});
the data: leagueInfo is the data that I am supplying from the server. I store these in .json files in a folder called json so that I can find them easily and add to them easily.
I just import them into my unit test at the top of the file like this:
// Test Data
import leagueInfo from './__json__/LeagueInfo.json';
This all works great when I am happy having jest fulfill the axios requests in the order that they are called. But today, after many years of doing this, I ran across a situation where I wanted to test that resolving two requests in the opposite order still produced the proper result.
in the then callbacks, you could define some timeouts, setting different timeout values depending on which should resolve first/last
const delay = (ms) => () => new Promise((res) => setTimeout(res, ms))
axios.request('get', url1)
.then(delay(10))
.then(() => { do something }); // resolves first
axios.request('get', url2)
.then(delay(20))
.then(() => { do something else }); // resolves last
edit...
I just realised this is probably your production code, not test code. Either way you could apply the same thing to axios mock, i.e. apply a delay to a chained then callback

Best practise to handle with responses and incoming props

with redux, we uses actions to handle with crud operations. But I stuck at some points. If we send async requests inside of component. We can easly handle with response. But when we send request through actions, we dont know what happened. Is request send successfully ? it took how much amount of time ? What kind of response is returned ? we don't know that
I will clarify question with samples..
lets update a post.
onClick () {
postsApi.post(this.state.post) | we know how much time
.then(res => res.data) | has took to execute
.then(res => { | request
console.log(res) // we have the response
})
.catch(err => console.log(error))
}
But if we use actions
onClick () {
this.props.updatePost(this.state.post) // we know nothing what will happen
}
or handling with incoming props. lets say I have fetchPost() action to retrieve post
componentDidMount(){
this.props.fetchPost()
}
render method and componentDidUpdate will run as well. It's cool. But what if I want to update my state by incoming props ? I can't do this operation inside of componentDidUpdate method. it causes infinity loop.
If I use componentWillUpdate method, well, things works fine but I'm getting this warning.
Warning: componentWillReceiveProps has been renamed, and is not
recommended for use. Move data fetching code or side effects to
componentDidUpdate. If you're updating state whenever props change,
refactor your code to use memoization techniques or move it to static
getDerivedStateFromProps
I can't use componentDidUpdate method for infinty loop. Neither getDerivedStateFromProps method because it's run everytime when state change.
Should I continue to use componentWillMethod ? Otherwise what should I use and why (why componentWillMethod is unsafe ?)
If I understand correcty, what you would like to do is to safely change your local state only when your e.g. updatePost was successful.
If indeed that is your case, you can pass a callback function on your updatePost and call this as long as your update was succefull.
successfulUpdate() {
// do your thing
this.setState( ... );
}
onClick () {
this.props.updatePost(this.state.post, this.successfulUpdate) // we know nothing what will happen
}
UPDATE:
You can also keep in mind that if your action returns a promise, then you can just use the then method:
onClick () {
this.props.updatePost(this.state.post).then(this.onFulfilled, this.onRejected)
}
I think we can use redux-thunk in this cases. What if we dispatch an async function instead of dispatch an action object?
"Neither getDerivedStateFromProps method because it's run everytime when state change." - does it matter? You can avoid setting state with every getDerivedStateFromProps call by using a simple condition inside.
Example:
static getDerivedStateFromProps(props, state) {
if (props.post !== state.post) { // or anything else
return {
post: props.post,
};
}
return null;
};
An infinite loop will not occur.
Here is my way for such cases. We can redux-thunk for asynchronous calls such as api call. What if we define the action that returns promise? Please check the code below.
actions.js
export const GetTodoList = dispatch => {
return Axios.get('SOME_URL').then(res => {
// dispatch some actions
// return result
return res.data;
});
}
TodoList.js
onClick = async () => {
const { GetTodoList } = this.props;
try {
const data = await GetTodoList();
// handler for success
this.setState({
success: true,
data
});
} catch {
// handler for failure
this.setState({
success: fail,
data: null
});
}
}
const mapStateToProps = state => ({
GetTodoList
});
So we can use actions like API(which returns promise) thanks to redux-thunk.
Let me know your opinion.

waiting observable subscribe inside foreach with forkJoin

I am trying to populate an array in my component called conventions which is an array of convention.
Each organization has a list of contracts, and each contract has a convention id, with this id i got the convention.
I use getOrganizationForUser to get current organization and then get the list of contract.
Then i use the convention id from contract to call the second API to get the convention.
Currently, my code looks something like this:
public getOrganizationForUser(): Observable<Organization> {
return this.httpClient
.get<Organization>(`${c.serviceBaseUrl.sp}/organizationByUser`)
.pipe(catchError((err, source) => this.responseHandler.onCatch(err, source)));
}
public getById(id: number) {
return this.httpClient
.get<Convention>(`${c.serviceBaseUrl.sp}/conventions/` + id)
.pipe(catchError((err, source) => this.responseHandler.onCatch(err, source)));
}
ngOnInit() {
this.OrganizationService.getOrganizationForUser().subscribe((organization: Organization) => {
organization.contracts.forEach((contract) => {
this.conventionService.getById(contract.conventionId).subscribe((convention: Convention) => {
this.conventions.push(convention);
})
})
})
}
I understand that I can create an array of observables, and use Observable.forkJoin() to wait for all these async calls to finish but I want to be able to define the subscribe callback
function for each of the calls since I need a reference to the process. Any ideas on how I can go about approaching this issue?
i tried with this function but always is return understand
getTasksForEachProcess(): Observable<Array<any>> {
let tasksObservables = this.organizationService.getOrganizationForUser().pipe(map((organization: Organization) => {
organization.contractOrganizations.map(contract => {
return this.conventionService.getById(contract.conventionId).subscribe(convention =>
this.conventions.push(convention)
)
});
})
);
return forkJoin(tasksObservables);
};
ngOnInit() {
this.getTasksForEachProcess().subscribe(item => {
console.log(item);
}
}
First of all I am not sure of what your are really trying to achieve, since I do not understand what you mean by
I want to be able to define the subscribe callback function for each
of the calls since I need a reference to the process
Anyways, in a situation like the one you describe, I would do something like this
public getOrganizationForUser(): Observable<Organization> {
return this.httpClient
.get<Organization>(`${c.serviceBaseUrl.sp}/organizationByUser`)
.pipe(catchError((err, source) => this.responseHandler.onCatch(err, source)));
}
public getById(id: number) {
return this.httpClient
.get<Convention>(`${c.serviceBaseUrl.sp}/conventions/` + id)
.pipe(catchError((err, source) => this.responseHandler.onCatch(err, source)));
}
ngOnInit() {
const obsOfObservables = this.OrganizationService.getOrganizationForUser()
.pipe(
map(organization => organization.contracts),
map(contracts => contracts.map(contract => this.conventionService.getById(contract.conventionId)))
);
obsOfObservables
.pipe(
switchMap(conventionObservables => forkJoin(conventionObservables))
)
.subscribe(
conventions => { // do stuff with conventions }
)
}
The key points here are the following.
Via getOrganizationForUser() you get an Observable which emits the Organization. The first thing you do you transform the object emitted by the Observable into an Array of contracts with the first map operator.
The second map operator transforms the Array of contracts into an Array of Observables of conventions. To perform this transformation we use the map method of Array within the map operator of Observable. This may be a bit confusing, but it is worth understanding.
If we stop here, what we have is obsOfObservables, i.e. an Observable which emits an Array of Observables.
We then pass the Array of Observables emitted by obsOfObservables to the forkJoin function, which in itself returns an Observable. Since we actually interested in what is notified by the Observable returned by forkJoin, i.e. we are interested in the conventions, then we need to switch from the first Observable to the second one, and this is done via switchMap operator.
The net result is an Observable which returns an Array of conventions. Consider that the constant obsOfObservables has been added as an attempt to clarify the reasoning and it is totally unnecessary (as Barney Panofsky would say).
I have not simulated the whole thing, so I hope I have not inserted mistakes, but more or less this is the thought process I would use in this case.
Last note, be generally suspicious when you have subscribe within subscibe.
I agree with Picci's logic in thinking through his answer. Here is a slight variation to what he proposed, though like him I have not rigorously tested this and there may be some errors.
The logic to this is that what you ultimately want is an array of convention, and producing an array from observables is what 'zip' does. So here is the flow:
get an organization, then create a stream of observables out of the organization.contracts array using rxjs' from.
each item in that stream will be a contract which will then be transformed (using map) into a convention based on and API lookup using the contract.conventionId property.
this whole resulting stream of observables of conventions will finally be transformed back into an array by the wrapping zip, and delivered as an observable that can be subscribed to resulting in the wanted array of conventions.
Here is the code:
ngOnInit() {
zip( this.OrganizationService.getOrganizationForUser()
.pipe(
map((organization: Organization) =>
from(organization.contracts).pipe(
map(contract => this.conventionService.getById(contract.conventionId))
)
)
)
)
.subscribe((conventions: Convention[]) => this.conventions = conventions)
}

switchMap operation only running on first call?

I have an angular application that makes a request to an Http service and calls a switchMap on another Http service. For some reason the request in the switchMap only runs the first time the parent call is called. Otherwise the parent request fires and the switchMap one doesn't, here is the code:
this._receivableService.newTenantDebitCredit(tenantCredit)
.take(1)
.switchMap(result =>
// Refresh the lease receivables before giving result
this._receivableService.getAll({
refresh: true,
where: { leaseId: this.leaseId }
}).take(1).map(() => result)
)
.subscribe(
...
)
How can I make the getAll request in the switch map run every time the newTenantDebitCredit method is called above it?
Edit: Here is the entirety of the function that is called on click. when i click the button the first time for a given unit both methods are executed. If I try a Unit that has already had that method called (without a refresh) only the first method is executed. I realize a lot of this may not be clear it's a rather large project at this point.
public submitTenantCredit() {
this.isLoading = true;
let tenantCredit: NewTenantDebitCreditData;
let receivableDefinitions: ReceivableDefinition[] = [];
// construct receivable defintions for NewTenantDebitData model
receivableDefinitions = this._constructReceivableDefinitions();
// construct data we will be POSTing to server.
tenantCredit = new NewTenantDebitCreditData({
siteId: this._apiConfig.siteId,
leaseId: this.leaseId,
isCredit: true,
receivables: receivableDefinitions,
reason: this.actionReason
});
// make service call and handle response
this._receivableService.newTenantDebitCredit(tenantCredit)
.take(1)
.switchMap(result =>
// Refresh the lease receivables before giving result
this._receivableService.getAll({
refresh: true,
where: { leaseId: this.leaseId }
}).take(1).map(() => result)
)
.take(1)
.subscribe(
(receivables) => {
this.closeReasonModal();
let refreshLeaseId = this.leaseId;
this.leaseId = refreshLeaseId;
this.isLoading = false;
this.refreshBool = !this.refreshBool;
this._debitCreditService.refreshUnitInfo();
this._notifications.success(`The tenant credit for ${this.customerName} - Unit ${this.unitNumber} was submitted successfully`);
},
(error) => {
console.error(error);
this.isLoading = false;
}
)
}
If it helps newTenantDebitCredit() is a HTTP POST request and getAll() is a GET request.
You used take operator. When your service observable will emit then take operator will execute first and take will chain only first emit from observable. Subsequent emit will not taken by your code.
If you want to take all emits from observable then remove take from your code.
Hope it will help.
Testing the Rx code in isolation, here's a mockup. The console logs happen each time, so I think the Rx you're using is ok.
The best guess at a likely culprit is this.refreshBool = !this.refreshBool, but we'd need to see the internals of newTenantDebitCredit and getAll to be definitive.
// Some mocking
const _receivableService = {
newTenantDebitCredit: (tc) => {
console.log('inside newTenantDebitCredit')
return Rx.Observable.of({prop1:'someValue'})
},
getAll: (options) => {
console.log('inside getAll')
return Rx.Observable.of({prop2:'anotherValue'})
}
}
const tenantCredit = {}
// Test
_receivableService.newTenantDebitCredit(tenantCredit)
.take(1)
.switchMap(result => {
console.log('result', result)
return _receivableService.getAll({
refresh: true,
where: { leaseId: this.leaseId }
})
.take(1)
.map(() => result)
})
.take(1)
.subscribe(
(receivables) => {
console.log('receivables', receivables)
//this.refreshBool = !this.refreshBool;
},
(error) => {
console.error(error);
}
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
First of all, this has nothing to do with the switchMap operator.
Normaly removing the take(1) would cause this behaviour. In this case it wouldn't because it itsn't a so called hot observable.
The problem is that you are using a http.post. This is a cold observable which means it will only return a value once. That is also the reason why you don't need to unsubscribe. It will NEVER fire twice. Possible sollutions might be:
Using web sockets to get realtime data.
Creating a timer which will periodically fetch the data.
Simply get the data again whenever you need it.
The way you are asking the question
How can I make the getAll request in the switch map run every time the newTenantDebitCredit method is called above it?
actually sounds to me as if you are calling only newTenantDebitCredit from somewhere in your code, expecting the second request to happen; so I think this might be a misunderstanding of how observable chains work. Let's make an example:
const source$ = Observable.of(42);
source$
.map(value => 2 * value)
.subscribe(console.log);
source$
.subscribe(console.log);
What would you expect this to log? If your answer is "It would log 84 twice", then that is wrong: it logs 84 and 42.
Conceptually, your situation is the same. The second request only happens when the observable returned by newTenantDebitCredit() emits; it will not happen anytime some caller calls newTenantDebitCredit. This is because observable chains do not mutate an observable in-place, they only ever return a new observable.
If you want the second request to happen, you have to actually change the definition of the newTenantDebitCredit method to return an observable set up to perform the second request; alternatively, set up a chained observable that you subscribe to instead of calling newTenantDebitCredit.
Not really an answer but I did solve my problem. It will almost certainly be of no use to anyone BUT it was an issue in the receivableService it was not properly cheeking the boolean: refresh and was pulling values from cache after the first time.

Testing fetch() method inside React component

I have an App component that is responsible for rendering child input components, it is also responsible for handling fetch requests to the Twitch API via a method called channelSearch. I have tried to adhere to suggested best practices outlined here for working with ajax/fetch with React.
The method is passed down through props and called via a callback.
Note the fetch method is actually isomorphic-fetch.
channelSearch (searchReq, baseUrl="https://api.twitch.tv/kraken/channels/") {
fetch(baseUrl + searchReq)
.then(response => {
return response.json();
})
.then(json => {
this.setState({newChannel:json});
})
.then( () => {
if (!("error" in this.state.newChannel) && this.channelChecker(this.state.newChannel._id, this.state.channelList) ) {
this.setState(
{channelList: this.state.channelList.concat([this.state.newChannel])}
);
}
})
.catch(error => {
return error;
});
}
I am currently trying to write a test for the channelSearch method. I am currently using enzyme and jsdom to mount the entire <App> component in a DOM. Find the child node with the callback, simulate a click (which should fire the callback) and check to see if the state of the component has been changed. However, this does not seem to work.
I have also tried calling the method directly, however, I run into problems with this.state being undefined.
test('channel search method should change newChannel state', t => {
const wrapper = mount(React.createElement(App));
wrapper.find('input').get(0).value = "test";
console.log(wrapper.find('input').get(0).value);
wrapper.find('input').simulate("change");
wrapper.find('button').simulate("click");
console.log(wrapper.state(["newChannel"]));
});
I am really lost, I am not sure if the method itself is poorly written or I am not using the correct tools for the job. Any guidance will be greatly appreciated.
Update #1:
I included nock as recommended in comments, test now looks like this:
test('channel search method should change newChannel state', t => {
// Test object setup
var twitch = nock('https://api.twitch.tv')
.log(console.log)
.get('/kraken/channels/test')
.reply(200, {
_id: '001',
name: 'test',
game: 'testGame'
});
function checker() {
if(twitch.isDone()) {
console.log("Done!");
console.log(wrapper.state(["newChannel"]));
}
else {
checker();
}
}
const wrapper = mount(React.createElement(App));
wrapper.find('input').get(0).value = "test";
wrapper.find('input').simulate("change");
wrapper.find('button').simulate("click");
checker();
});
This still does not seem to change the state of the component.
fetch is asynchronous but you're testing synchronously, you need to either mock fetch with a synchronous mock or make the test asynchronous.
nock may work for you here.
I suggest you create a sample of your test using plnkr.
I agree with Tom that you're testing synchronously. It would of course be helpful to show off your actual component code (all of the relevant portions, like what calls channelSearch, or at the least describe it by saying e.g. "channelSearch is called by componentDidMount()". You said:
I run into problems with this.state being undefined.
This is because this.setState() is asynchronous. This is for performance reasons, so that React can batch changes.
I suspect you'll need to change your code that is currently:
.then(json => {
this.setState({newChannel:json});
})
to:
.then(json => {
return new Promise(function(resolve, reject) {
this.setState({newChannel:json}, resolve);
})
})
Note that your checker() method won't work. It's looping, but twitch.isDone() will never be true because it never has a chance to run. Javascript is single threaded, so your checker code will run continuously, not allowing anything else in between.
If you set up the plnkr, I'll take a look.
Refactor out the fetch code from the component then pass it it to the component as a callback function in the properties.
export class Channel extends React.Component {
componentDidMount() {
this.props.searchFunction().then(data => this.setState(data));
}
render() {
return <div>{this.state}</div>;
}
}
Uage:
function channelSearch(name) {
return fetch(`https://api.twitch.tv/kraken/search/channels?query=${name}`);
}
<Channel searchFunction={channelSearch} />
Now you can test the API functionality independently of the component.

Categories

Resources