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);
})
Related
Hey Guys I'm using Firebase Realtime Database to fetch some data for my React.js Web App.
There is a useState called Corr_User that should store the value of correct username.
I have a function to fetch the username from Firebase : -
function Login(){
const path = firebase.database().ref("users").child(username);
path.once("value")
.then(snapShot => {
setCorr_User(snapShot.child("username").val());
})
When I do this console.log(Corr_User) it prints a empty string indicating that the useState is not updated.
For confirmation, I also logged the snapshot.val(). This printed the appropriate value.
I could understand from this that Firebase is too slow in returning response hence my useState is not getting updated. Is my understanding correct? Is there any way to make my Login() function wait for the response?
Please Help !! Thanks in Advance.
EDIT -
Ok there was a bit of confusion I guess. The code goes likes this
function Login(){
....
.once("value")
.then(snapShot => { // After fetching the data
setCorr_User(snapShot.child("username").val()); // Assigning the value
console.log(snapShot.val()); // This should be logged first
}) // End of function snapShot()
// This is still inside Login()
console.log(Corr_User) // But this is logged first
} // End of Login()
It is from this, I was able to guess that Firebase is too slow in responding and useState is not getting updated accordingly as snapShot.val() is still empty.
Data is loaded from Firebase asynchronously, and the then is called when that data is available. What this means in practice is that your console.log(Corr_User) is called before console.log(snapShot.val()). For this reason, any code that needs data from the database must be inside the then callback, or be called from there.
This is an incredibly common stumbling block for developers new to asynchronous APIs, so I recommend studying some of these links:
Why Does Firebase Lose Reference outside the once() Function?
Best way to retrieve Firebase data and return it, or an alternative way
Firebase Query inside function returns null
Passing variable in parent scope to callback function
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 am getting some data from my server and want to do some stuff with it inside the .ts file. It's an elementary thing that I don't understand about Typescript/angular so far...Hope someone can help me here
user: any;
public doStuff(){
alert(this.user.username);
}
a user is an object having different properties like 'username' that is initialized on the ngOnInit() block.
I´m setting it in the ngOnInit method. The service is injected correctly and following code works properly
ngOnInit() {
this.authService.getProfile().subscribe(profile =>{
this.user= profile.user;
this.initStuff();
},
err => {
console.log(err);
return false;
});
}
it's alerting the username as intended...but as soon as I move the method call of the doStuff() Method outside that Codeblock, it´s not working anymore, in the browser-console it says "cannot read property 'value' of undefined" - why is it undefined? If I use {{user.username}} in the component.html, it also shows me the correct username
ngOnInit() {
this.authService.getProfile().subscribe(profile =>{
this.user= profile.user;
},
err => {
console.log(err);
return false;
});
this.initStuff(); // why cant i call it here? Its where I also call all of my other doStuff() methods
}
It is not working because getProfile() is async operation. If you want to do stuff with server data it has to be done in subscribe next() method. Next method is called when aysnc operation has new value.
If you call initStuff() outside of next() method, it will be executed immediately that's why your getting "cannot read peoperty 'value' of undefined".
Hope this can help you.
If I have understood all correctly you are trying to call not yet received data and it is reason of you console error. In described function you use Observable (RxJs) and is function is async. If you want to do something with variable 'profile' you should to place operation in Observable callback, is meant in body subscribe's call method.
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.
I feel like I am missing something here. I have a service that grabs some data. I convert it to a promise and then try and work on the data in a seperate method.
When once it hits the method I loose the ability to access my objects that i would normally access from this.whatever. If I leave all the code from the addJobsToTree in the then block, it works fine. I can also access this from every where else in the component. I'm sure i'm doing something dumb but can't figure it out.
ngOnInit(){
this._scheduleDataService.getSavedScheduleData(this.buildDateStringFromCalendar(),1004)
.toPromise()
.then(this.addToJobsTree);
}
private addToJobsTree(res){
for(let xx of res){
this._sharedService.jobs.push(xx); //Comes back as cannot read _sharedService of null
console.log(this._sharedService.jobs);
}
}
It's because you reference a function and you lose the context of the function. To fix that you need to explicitly link the function to an object.
You can use either the bind method:
ngOnInit(){
this._scheduleDataService.getSavedScheduleData(this.buildDateStringFromCalendar(),1004)
.toPromise()
.then(this.addToJobsTree.bind(this); // <-----
}
(note: here is the drawback to using the bind method with TypeScript: https://basarat.gitbooks.io/typescript/content/docs/tips/bind.html)
or an arrow function to fix that:
ngOnInit(){
this._scheduleDataService.getSavedScheduleData(this.buildDateStringFromCalendar(),1004)
.toPromise()
.then((data) => { // <-----
this.addToJobsTree(data);
});
}