unable to assign data from response data in Angular - javascript

I have a function that should return an array of objects when called. The function looks like this
loadTodo(): Todo[]{
var data
this.http.get(`${this.API_URL}todos`).toPromise().then(res => {
data = res.json()
}, error => {
console.log(error)
})
return data}
This results in unexpected behavior where data variable gets assigned correctly inside the success response block but is undefined when accessed outside the response block.
The function is assigned to a variable with type Todo[] and is invoked immediately when the variable is declared. I am quite new to TypeScript and Angular but not to JavaScript. Am I missing something with scope/closure of the function or is this issue related to TypeScript/Angular?
Whole class looks like this:
export class TodoDataService {
API_URL: String = 'http://localhost:3000/'
todos: Todo[] = this.loadTodo();
constructor(private http: Http) {
}
loadTodo(): Todo[]{
this.http.get(`${this.API_URL}todos`).toPromise().then(res => {
this.parcedTodos = res.json()
console.log('inside function')
console.log(this.parcedTodos)
}, error => {
console.log(error)
})
console.log('outside function')
console.log(this.parcedTodos)
return this.parcedTodos
}
}

http.get() is asynchronous, which means that when you try to print parcedTodos outside the then callback, it will still be undefined.
Asynchronous programming in JS

It is happening because http calls are asynchronous.
You need to make sure you are accessing data only after call is completed.
export class TodoDataService {
API_URL: String = 'http://localhost:3000/'
todos: Todo[] = this.loadTodo();
constructor(private http: Http) {
}
loadTodo(): Todo[]{
this.http.get(`${this.API_URL}todos`).toPromise().then(res => {
this.parcedTodos = res.json()
console.log('inside function')
console.log(this.parcedTodos)
}, error => {
console.log(error)
},
{
console.log(this.parcedTodos);
// This is where your call gets completed. Here you can access assigned data or call another function where you can access data.
})
console.log('outside function')
console.log(this.parcedTodos) // This is called before asynchronous call is completed. Thats why it is undefined yet.
return this.parcedTodos
}
}
Hope this helps.

this.http.get(whatever) is an async call.
Your data is undefined because you're accessing it before it is actually initialized. i.e. you're initializing it inside the success handler (the first argument to then), and probably are accessing it before initialization.
All you need to do is make sure you're doing so after the success or error handler. use Observable

I think that using res.json() not is neccesary because angular pipes already doing this works. Do you try to assign to variable res directly?
As others friends says, you are doing bad some things.
First: you must read about asynchronous methods
Second: use Observables importing rxjs/Observable; and follow its callbacks flow
Example
export class TodoDataService {
API_URL: String = 'http://localhost:3000/'
todos: Todo[] = this.loadTodo();
constructor(private http: Http) {
}
loadTodo() : Observable<Todo[]>{
return this.http.get(`${this.API_URL}todos`);
}
}
And other class consummes this method
todoDataService.loadTodo().subscribe(
(response) => {
console.log("Future response", response);
}
);

Related

Can't loop through an array of objects in Typescript (Angular)

in my project I'm fetching data from an API (trip entities). This is my model:
//Trip.model.ts
export class Trip{
created_at?:Date;
updated_at?:Date;
.... bunch of fields
}
In my component I'm fetching the data and assigning it to the trips variable. However, when I'm trying to access any of the items in the trips array I get 'undefined'. I also can't loop through it, I tried both forEach and for...in/of.
I tried using an interface instead of a class but with no luck. How can I loop through that array of objects in order to use the data in it?
Component's code:
userName:string='';
trips:Trip[]=[];
moment:any=moment;
usersData:any={};
constructor(private auth: AuthService,
private storage: LocalStorageService,
private translate: TranslateService,
private tripService: TripService){}
ngOnInit(): void {
console.log(this.translate.currentLang)
this.userName=localStorage.getItem('username')!;
this.fetchTrips();
this.fetchPics();
}
fetchTrips() {
this.tripService.getTrips().subscribe({
next: data => {
data[0].data.forEach((value) => {
let trip: Trip = plainToInstance(Trip,value);
this.trips.push(trip);
});
}, error: error => {
console.log(error);
}
});
}
//fetchPics because I want to extract
//user's profile pics' urls from the trips array
fetchPics(){
console.log(this.trips);
console.log(this.trips[0]);
this.trips.forEach((trip)=>{
console.log(trip);
});
}
getTrips service method:
getTrips(){
return this.http.get<any>(Api.API+Endpoints.TRIP);
}
This is what shows when I
console.log(this.trips)
after assignment.
Data from the API:
Pictures cropped to make them more readable.
You are trying to get access to this.trips value before you actually have your data on it.This happens becouse you get that data asynchronously, just inside the subscribe of this.tripService.getTrips()
So, in order to solve your problem, you just need to move this invoke:
this.fetchPics();
inside the subscribe of fetchTrips() method, like this:
fetchTrips() {
this.tripService.getTrips().subscribe({
next: data => {
data[0].data.forEach((value) => {
let trip: Trip = plainToInstance(Trip,value);
this.trips.push(trip);
this.fetchPics();
});
}, error: error => {
console.log(error);
}
});
}
The function fetchPics() executes before your getTrips call ends. You need to call the method only after your HTTP call ends, and after you populate your trips array successfully.
fetchTrips() {
this.tripService.getTrips().subscribe({
next: data => {
//Populate your trips array
data[0].data.forEach((value) => {
let trip: Trip = plainToInstance(Trip,value);
this.trips.push(trip);
});
// this is where you need to call
this.fetchPics();
}, error: error => {
console.log(error);
}
});
}
This is happening because of JS is asynchronous. you are making an http request here, that may take some time to get data from server. let's assume that might take 1 minute untill then compiler will not stop it's execution process In that 1 min of time it will execute next statements.because of that your fetchpics() method is being executed before fecthtrips() execution done. To overcome this we can use async, await as like below.
async fetchTrips() {
this.tripService.getTrips().subscribe({
next: data => await {
data[0].data.forEach((value) => {
let trip: Trip = plainToInstance(Trip,value);
this.trips.push(trip);
});
}, error: error => {
console.log(error);
}
});
}

Angular - Using a service property set by callback in another component

I am trying to use a DataService property myData that is waiting for callback. But it is undefined when I call in DataComponent. How can I access and use it there?
export class DataService {
public myData;
constructor(private http: HttpClient) {
this.load().then((data) => {
this.myData = data
})
}
load() {
return new Promise((resolve) => {
this.http.get('https://reqres.in/api/users').subscribe(
(res: any) => {
console.log(res.data)
resolve(res.data)
},
(error) => {
console.log(error);
}
)
})
}
}
export class DataComponent implements OnInit {
constructor(private dataService: DataService) {
this.prepareData();
}
prepareData() {
console.log(this.dataService.myData)
}
ngOnInit(): void {
}
}
Here is the source code: https://stackblitz.com/edit/angular-ivy-kbpdpo
You are running into a race condition since this is an asynchronous function.
This change works: https://stackblitz.com/edit/angular-ivy-vf3llg
Consider reading up on https://angular.io/guide/http
Personally, I just have services return raw data and manipulate it elsewhere, but if needed you can tap into the response as I have shown i the updated example.
This question and answer are probably really a duplicate of this question...
What are pipe and tap methods in Angular tutorial?
your load() method is asynchronous, that means that it can return the response after 2 hours, so it will execute your callback after 2 hours, and you are asking myData synchronously which means that you are asking it right now, so it won't work.
you have to wait until the answer is returned, in your code there is no chance to accomplish this, so either remove yourData field and just subscribe it into the component, or create BehaviorSubject and emit value to the component

use data outside of .subscribe in typescript file

I am very new to typescript/ionic 4. I am trying to access data stored in firebase and use it in my typescript file. when in .subscribe I can display the data as requested. but this is not what I am looking for. I need to perform the calculation outside of .subscribe on my page.ts .
I have seen many similar issues, but I cannot seem to get a solution.
Here is my Typescript services file
export interface Place{
title: string;
type: string;
latitude: number;
longitude: number;
}
export class PlaceService {
placess: Place[];
place: Place;
private placesCollection: AngularFirestoreCollection<Place>;
private places: Observable<Place[]>;
constructor(db: AngularFirestore) {
this.placesCollection = db.collection<Place>('places');
this.places = this.placesCollection.snapshotChanges().pipe(
map(actions =>{
return actions.map(a => {
const data = a.payload.doc.data();
const id = a.payload.doc.id;
return{ id, ...data};
});
})
);
}
getPlaces() {
return this.places;
}
}
and the relevant part in my page typescript
import { PlaceService, Place } from '../services/place.service';
places: Place[];
ngOnInit() {
this.placeService.getPlaces()
.subscribe(res =>{
this.places = res;
console.log(this.places[0].title);//WORKS
});
console.log(this.places[0].title);//FAILED
}
I get the following error message:
MapPage_Host.ngfactory.js? [sm]:1 ERROR TypeError: Cannot read property '0' of undefined
Your problem is that your code works as you wrote it. When the page initializes the ngOnInit is called. Inside the code goes to the first element (this.placeService.getPlaces() ... ) and immediately goes to the seconde element (console.log(this.places[0]). This throws the error, because the places variable is not yet set from your call to the placeService and is currently undefined.
ngOnInit() {
this.placeService.getPlaces() // called first
.subscribe(res =>{
this.places = res;
console.log(this.places[0].title);
});
console.log(this.places[0].title); // called second (undefined error)
}
If you call a function after you set the places variable, the second console.log() will work.
ngOnInit() {
this.placeService.getPlaces()
.subscribe(res =>{
this.places = res;
console.log(this.places[0].title);
this.showFirstTitle(); // this.places is set
});
}
showFirstTitle() {
console.log(this.places[0].title); // will work
}
.subscribe method has to complete( ajax request has to be 200-OK), inside subscribe method you can store into your local variables, Then further modifications are possible.
you can not use a variable which has no data.
this.placeService.getPlaces()
.subscribe(res =>{
this.places = res;
});
will take some seconds to complete the ajax call and fetch the response and storing in "Places".
workaround(not recommended) use set timeout function wait for at least 2 sec. increment seconds until you find a minimal seconds that request and response completed.
then you can do some calculations on this.places.

Which RxJS type to use when a method may or may not fetch data asynchronously?

Imagine we have the following factory:
#Injectable()
export class DataClassFactory {
constructor(
private dataService: DataService,
) { }
public createThing(initialData?: InitialData): AsyncSubject<DataClass> {
let dataClass: AsyncSubject<DataClass> = new AsyncSubject<DataClass>();
if (!!initialData) {
dataClass.next(new DataClass(initialData));
dataClass.complete();
} else {
this.dataService.getData().subscribe((dataResponse) => {
dataClass.next(new ReportRequest(dataResponse));
dataClass.complete();
});
}
}
return dataClass;
}
}
We inject this factory, invoke the createThing method, and subscribe to the response in some component. I originally tried to use a plain Subject, but then I realized that in the case where we already have initial data, next() is called before the response is returned, so the subscriber in the component never gets that value.
My question is: is this correct situation in which to use an AsyncSubject, or is there a different/better way to handle this sort of method that has potential synchronous and asynchronous timelines?
I would do something along these lines
public createThing(initialData?: InitialData): Observable<DataClass | ReportRequest> {
if (!!initialData) {
const data = new DataClass(initialData);
return of(data);
} else {
return this.dataService.getData()
.pipe(map(dataResponse => new ReportRequest(dataResponse));
}
}
Whoever calls createThing would get an Observable to which it would have to subscribe.
This Observable would emit an instance of DataClass if initialData is not null, otherwise it would return and instance of ReportRequest as soon as dataService responds.

Angular 2+ wait for subscribe to finish to update/access variable

I am having an issue with my variables being undefined. I am certain this is because the observable hasn't finished. Here is the part of my code in my .ts file that is causing the issue. (I'm placing the minimum code required to understand the issue. Also myFunction gets called from a click event in the HTML).
export class myClass {
myVariable: any;
myFunction() {
this.myService.getApi().subscribe(data => {
this.myVariable = data;
});
console.log(myVariable) --> undefined
}
}
So this piece of code calls a function in my service that returns some data from an API. The issue is that when I try to access the variable myVariable right outside of the subscribe function it returns undefined. I'm sure this is because the subscribe hasn't finished before I try to access myVariable
Is there a way to wait for the subscribe to finish before I try to access myVariable?
why not create a separate function and call it inside the subscription.
export class myClass {
myVariable: any;
myFunction() {
this.myService.getApi().subscribe(data => {
this.myVariable = data;
this.update()
});
this.update()
}
update(){
console.log(this.myVariable);
}
}
As you know subscriptions are executed when server return data but the out side of subscription code executed synchronously. That is why console.log outside of it executed. The above answer can do your job but you can also use .map and return observable as shown below.
let say you are calling it from s service
export class myClass {
myVariable: any;
// calling and subscribing the method.
callingFunction() {
// the console log will be executed when there are data back from server
this.myClass.MyFunction().subscribe(data => {
console.log(data);
});
}
}
export class myClass {
myVariable: any;
// this will return an observable as we did not subscribe rather used .map method
myFunction() {
// use .pipe in case of rxjs 6
return this.myService.getApi().map(data => {
this.myVariable = data;
this.update()
});
}
}

Categories

Resources