I am from a Java background. I started learning Angular2 a while back and working on it. In one of my projects, I have come across a situation which I cannot understand.
For my pagination implementation, I am taking the number of all tenders available in the database using Angular2 observables. After getting the value, I just log it into the console just to make sure the code works fine. But it prints undefined.
This is the relevant part of my code.
this.getNumberOfAllTenders();
console.log("number of tenders = "+this._numberOfAllTenders);
Here is the output
number of tenders = undefined
following is the method which takes the number of tenders from the back end
getNumberOfAllTenders(){
this._tendersService.getNumberOfAllTenders().
subscribe(
numberOfAllTenders => this._numberOfAllTenders = numberOfAllTenders,
error => this._error_all_numbers = error
);
console.log('+++++++++++++++++++ number of tenders in db = '+this._numberOfAllTenders);
}
In above code snippet also, there is a line to print to the console. The output of that too is undefined. But that line is executed once the variable is assigned the value obtained from the back end.
I am sure that, my service code gets the value from the backend. I tried printing it on my template. It prints the correct value.
Now my question is, why it prints 'undefined' in the console. Those variables are assigned values correctly. From what I know, once the function to assign the values to variables is called, the values should be available for the latter parts of the code.
Please clarify me on this. is the flow of code execution different in Angular2?
It prints undefined because observables run async and thus they aren't finished running by the time your console commands run. If you wanted to use console.log on the return value of the observable, you could move the console command to inside the subscribe function:
this._tendersService.getNumberOfAllTenders().
subscribe(
numberOfAllTenders => {
this._numberOfAllTenders = numberOfAllTenders;
console.log('+++++++++++++++++++ number of tenders in db = '+this._numberOfAllTenders);
},
error => this._error_all_numbers = error
);
When working with variables in your component that get values from observables, it can either be good to have a default value or, if necessary, use null checks with *ngIf:
*ngIf="_numberOfAllTenders"
You can also have your template subscribe to observables directly by using a syntax like this:
//in component
this._numberOfAllTenders = this._tendersService.getNumberOfAllTenders();
//in template
{{_numberOfAllTenders | async}}
This way this._numberOfAllTenders is of type Observable<number>. And your template can subscribe to it with the async pipe, which calls subscribe in the background and retrieves the value.
Since Angular 4, you can use async inside *ngIf statements and assign the value to a local template variable:
<div *ngIf="_numberOfAllTenders | async; let myValue">{{myValue}}</div>
The main thing is an observable does not return a value synchronously and so you need to adjust your other code to work with that. So if you are needing to use a value from one observable in order to call a second observable, you would need to look at chaining the observables together with flatMap or something like that:
firstObservable()
.flatmap(dataFromFirst => secondObservable(dataFromFirst)
.subscribe(dataFromSecond => //do something
Or if you need to save the value from the first observable before proceeding to the second:
firstObservable()
.flatmap(dataFromFirst => {
this.variable = dataFromFirst;
return secondObservable(dataFromFirst)
})
.subscribe(dataFromSecond => //do something
Hope this helps.
Related
When reading the code of a frontend written in Vue3 I stumbled upon a construction I have not seen so far and I have some problems understanding how it works. The basic idea is assigning to a reactive value the result of an asynchronous call (the JSON result of a fetch for instance).
The way I have been doing it is shown in the code below (Vue Playground). The setTimeout simulates the asynchronous call, a Promise is returned, and then acted upon via a then() that sets the reactive variable msg.
<script setup>
import { ref } from 'vue'
const msg = ref('one')
const asyncCall = () => new Promise((resolve, reject) => {
setTimeout(() => {
resolve("two");
}, 2000);
})
asyncCall().then(r => msg.value = r)
</script>
<template>
<h1>{{ msg }}</h1>
</template>
The code I stumbled upon (Vue Playground) takes a different approach: it creates a local reactive variable in a function, makes the asynchronous call and when it resolves it sets the value of that local ref variable. It is then returned (this is the visual sequence, the execution is different, see comments further down)
<script setup>
import { ref } from 'vue'
const msg = ref('x')
const asyncCall = () => {
const result = ref('one')
setTimeout(() => {
result.value = 'two'
}, 2000)
return result
}
msg.value = asyncCall()
</script>
<template>
<h1>{{ msg }}</h1>
</template>
It works as well, but I do not understand:
... why the local variable result is updated once we leave asyncCall (and return result with its default value of one because the callback in setTimeout() has not happened yet). It should be destroyed once the function is over.
... why I can return result (a pointer) and assign it to msg.value (a string) (and it works)
... why the displayed value ("one" and "two") have quotes. converted to its own question.
My question: is the second approach correct? recommended? It surely simplifies code because all the asynchronous part happens in the function, but I have never seen that approach.
What you call an async function is not, technically, an async function. It's a function returning a ref(). Immediately, without any asynchrony.
But that function also has a side effect (the setTimeout). At some point in the future, it updates the returned ref. Because you're assigning the result to another ref, when you modify the returned one's value you actually modify the value of the one it was assigned to.
To your point, it's perfectly legal. You can assign ref()s to a reactive()'s prop or to another ref() and if/when you update the returned ref's value, the one you passed it to will also update.
To make my point clearer, in the example you linked, msg.value starts off as x, it's immediately assigned the value of ref('one') and two seconds later the inner ref()'s value is changed to 'two'.
After this assignment, if you check msg.value, it is a ref(), it's no longer a string. But that's not a problem for <template> because it automatically unwraps any nested refs, until it gets to the actual .value.
However, be weary of using msg.value inside the controller (after the assignment), as you will have to unref it to get to the string value.
Another potential problem one might encounter with this technique is watch-ing msg will no longer detect changes after the assignment, because its value remains the same: result. To get past this problem, one would need to use { deep: true } in the watcher's options.
Calling foo two times creates two subscribers and that is not ok but even so the second call of foo seems to get stuck somewhere because console.log gets called only one time.
I know that I should not subscribe in this manner but I really do not see the solution to get myFunc to output the second result
const myFunc = functions.httpsCallable('myFunc')
function foo(inData){
myFunc({data:inData}).subscribe((res)=>{
console.log(res)
})
}
foo('aaaaa')
foo('bbbbb')
second attempt still no luck
myFunc(data){
return functions.httpsCallable('myFunc')(data)
}
async function foo(inData){
let res = await lastValueFrom(myFunc({data:inData}))
console.log(res)
}
foo('aaaaa')
foo('bbbbb')
The HttpsCallable function returns a promise by default. As you can see in this related question , there are a few differences between promises and observables, as it seems that you are already working with observables, I would try to convert the promise received from the function to an observable using this method, as suggested in this other question:
import { from } from 'rxjs';
const observable = from(promise);
Although, there are other methods suggested as well in the same question.
I would also like to point you to the Using observables to pass values, I think you might not have shared how you are creating the observer. I'm just guessing but you should also unsubscribe to clean up data ready for the next subscription. Also, missing next notification that is required.
As an extra, I would say that if you are not obligated to use observables, handle it as a promise.
Suppose I have a list of items that is queried from an API depending on a parameter that can be changed in the UI. When changing the value of this parameter, I dispatch an action:
this.store$.dispatch(new ChangeParameterAction(newParameterValue));
Now, on the receiving end, I want to trigger a new API call on every parameter change. I do this by subscribing to the store and then switching to the API observable. Then, I dispatch the result back into the store:
/** Statement 1 **/
this.store$.select(selectParameter).pipe(
switchMap(parameter => this.doApiCall$(parameter))
).subscribe(apiResult => {
this.store$.dispatch(new ResultGotAction(apiResult))
});
My UI is receiving the items by subscribing to
/** Statement 2 **/
this.store$.select(selectResults);
Now my question is: How can I join these two statements together so that we only have the subscription for Statement 1 for as long as the UI showing the results is active (and not destroyed)? I will always subscribe to the result of Statement 2, so Statement 1 will never be unsubscribed.
I've tried merging both observables and ignoring the elements for Statement 1, then subscribing tothe merged observables. But this looks like a very unreadable way for doing such a basic task. I think there must be a better way, but I can't find one. Hope you can help!
I can't tell exactly if this would run correctly, but I would go with moving the dispatch of ResultGotAction to a tap operator and then switching to this.store$.select(selectResults)
For example:
this.store$.select(selectParameter).pipe(
switchMap(parameter => this.doApiCall$(parameter)),
tap(apiResult => this.store$.dispatch(new ResultGotAction(apiResult))),
switchMapTo(this.store$.select(selectResults))
);
I am having issues parsing a JSON returned from my server, in my client code. If I send a basic request to my mongoDB server:
GET http://localhost:4000/food/
I get the following response, which is obviously an array of objects.
In my client, I have a state defined in the constructor:
this.state = {
index: 0,
foodList: []
};
And a function, callServer, which is called when the page is loaded using componentWillMount():
callServer() {
fetch("http://localhost:4000/food/")
.then(res => res.json())
.then(res => this.setState({ foodList: res }))
.catch(err => err);
}
This function populates the foodList in the files state with the server output - when I run console.log("debug:\n" + JSON.stringify(this.statefoodList[0])) the output is
Debug:
{"registerDate":"2020-04-01T14:34:04.834Z","_id":"5e66437d59a13ac97c95e9b9","image":"IMAGE","name":"Example 2","address":"BI1 111","type":"ExampleType1","price":"£Example Price","link":"example.com"}
Which shows that foodList is correctly set to be the output from the server.
The issue is, if I perform console.log("debug:\n" + JSON.stringify(this.state.foodList[0].name)) I get the error TypeError: Cannot read property 'name' of undefined.
I've been struggling with this issue for a while now - I do not understand why the client believes foodList to be undefined when you can see from prior testing that it is not undefined, and it is in a JSON format.
As a side note, if it is important, I call console.log() from inside the render() method, but before the return() value.
I'm very new to the React framework and JS as a whole, so any help would be appreciated :)
So, a good thing to note in react is that the state changes happen asynchronously. Another good thing to note is that chrome likes to be helpful with console logs and will show what the values evaluate to currently rather than at the time.
The main issue here (based on what you have written since we don't have code to look at) is that if the console log you have is run before the data call returns, then there won't be any data in the foodList array, so this.state.foodList[0] ===undefined and you can't access a property of undefined.
In react, if you want to console log a state change, a good option is to use the setState method's 2nd callback parameter. That is guaranteed to run after the state change.
callServer() {
fetch("http://localhost:4000/food/")
.then(res => res.json())
.then(res => this.setState({ foodList: res },()=>console.log(this.state.foodList[0].name))
.catch(err => err);
}
If you want to keep the console.log in the render function, you can either check to make sure that the array is actually populated or use the new optional chaining operator (which is supported in the latest create react app versions):
console.log("debug:\n" + JSON.stringify(this.statefoodList[0]?.name))
You try to console.log the first element of the array before the array is populated with the data from your server (as the ajax call takes some tome to execute), so position 0 of this.state.foodList is still undefined.
You can fix this by first checking if the array has a length like this
console.log(this.state.foodList.length && "debug:\n" + JSON.stringify(this.state.foodList[0].name))
I have a service that returns me the user's data according to the Token stored in the localStorage and everything is returned correctly until I get to my component.
The real problem is that I have the following code in my component.ts file:
Apparently, at least everything should work out for me. However, in the console it is possible to notice that even after I have assigned the return to my user property it is printed as undefined. As shown in the image below.
When trying to use in the HTML template I get messages saying that it was not possible to load data from the user property. I have already tried using async pipe like this: (user $ | async) as user. I tried to use the safe navigation like this:user?.email.
But it did nothing and I have no idea why this happens. Any help will be welcome!
User$ represents a stream, should be assigned this way:
export class {
// a stream type
user$: Obserable<User>;
ngOnInit() {
// a stream type
this.user$ = this.clienteHeaderService.getUser();
}
}
and the template add |async pipe.
this.user is not immediately available after you call subscribe(), it is very possible that the getUser() hasn't emitted any result by the time console.log(this.user) is called.
If you just wanted to see what's in this.user, you may try it in the callback of subscribe()
this.clientHeaderService.getUser().subscribe((response) => {
this.user = response;
console.log(this.user); // this.user should be available here
})
On the template side, you should be able to just access the user via {{ user }}.
I'd also suggest to use share your minimum reproducible code at https://stackblitz.com/, to get help more easily.
Subscribe almost work like promise in javascript and it has a callback
.subscribe(response=>this.user=response)
Callback are pushed to the end of current event loop .So you are accessing
console.log(this.user)
before callback in your subscribe get executed.
instead
try
.subscribe((response) => {
this.user=response;
//you can access value of this.user here or call other function here to
//access this.user
console.log(this.user);
})