I have a little problem with my code. I have 2 HTTP requests and after receiving the data I merge them together but when I try to loop through the final array I get the error displayed in the title.
I read other posts where they said that the returned thing is not an array but an object. I checked it and I receive a proper array. Here it is (sorry for the external link): https://gyazo.com/e41ba0d77765cac5b871a0c80c99073a
What am I doing wrong here? I used type 'any' everywhere to make things easier for the time being. I gotta add that I first used this logic in my component (the merging logic) and with an 'async as' pipe I could solve the problem (it did not work without the 'as ...' part, I got the same error.
My service code:
export class CryptoItemsService implements OnInit {
allCryptoInformationMerged$!: any;
constructor(private http: HttpClient) {}
ngOnInit(): void {}
// 1st HTTP request
fetchAllCryptos() {...}
// 2st HTTP request
fetchMetadata() {...}
mergeFetchedObjects() {
this.allCryptoInformationMerged$ = forkJoin(
this.fetchAllCryptos(),
this.fetchMetadata()
)
.pipe(
map(([prices, details]) => {
const mergedObjects: any = [];
for (const item of prices) {
const currentDetailObject = details.find(
(detailObject: any) => detailObject.id === item.id
);
mergedObjects.push({
...item,
...currentDetailObject,
});
}
return mergedObjects;
})
)
.subscribe((data) => {
this.allCryptoInformationMerged$ = data;
console.log(this.allCryptoInformationMerged$);
});
}
getallCryptos() {
this.mergeFetchedObjects();
return this.allCryptoInformationMerged$;
}
My component file code:
export class CryptoListitemsComponent implements OnInit {
cryptoItemGeneralDetails$!: any;
constructor(private cryptoItemsService: CryptoItemsService) {}
getCryptoData() {
this.cryptoItemGeneralDetails$ = this.cryptoItemsService.getallCryptos();
}
}
My template code:
<app-crypto-item
*ngFor="let item of cryptoItemGeneralDetails$; index as i; even as e"
[cryptoItem]="item"
[index]="i"
[even]="e"
></app-crypto-item>
CodePudding user response:
any
is actually making things harder for you, the typescript compiler would've told you a few things you did wrong otherwise.
For example you're assigning allCryptoInformationMerged$
two completely different data types.
Here you're assigning it a Subscription
.
this.allCryptoInformationMerged$ = forkJoin(...).pipe(...).subscribe(...);
Here you're assigning it the result of the Observable, I'm assuming it's an array.
.subscribe((data) => {
this.allCryptoInformationMerged$ = data;
});
Note that calling subscribe does not immediately execute the callback function (data)=>{this.allCryptoInformationMerged$ = data;}
. This function is only executed after your http requests emit results.
Then you return this property and use it in ngFor
, which throws an error because it is a Subscription
at first, which is not iterable.
getallCryptos() {
this.mergeFetchedObjects(); // Sets property to Subscription
return this.allCryptoInformationMerged$; // Returns before property is set to array
}
any
basically says "Leave me alone I know what I'm doing". In my opinion it's especially important to stay away from it if you're using data types / functions you're not familiar with, because you don't know what you're doing yet (no offense). Typescript is there to help you as a developer, so you're only shooting yourself in the foot by not taking advantage of it.
Here's what a working example should look like
Service
// I use any[] here because I don't know what your array contains
// But you should replace it with the actual data schema
getallCryptos(): Observable<any[]> {
// This is just your mergeFetchedObjects method without the subscribe bit
// I changed the forkJoin parameter to an array of Observables
// since the version you were using is deprecated
// forkJoin will also take the type of array returned
// I used [any, any] because I know it is length 2 but I don't know the types
return forkJoin<[any, any]>([this.fetchAllCryptos(), this.fetchMetadata()]).pipe(
map(([prices, details]) => {
const mergedObjects: any = [];
for (const item of prices) {
const currentDetailObject = details.find(
(detailObject: any) => detailObject.id === item.id
);
mergedObjects.push({
...item,
...currentDetailObject,
});
}
return mergedObjects;
})
);
}
Component TS
export class CryptoListitemsComponent {
cryptoItemGeneralDetails$ = this.cryptoItemsService.getAllCryptos();
constructor(private cryptoItemsService: CryptoItemsService) {}
}
Component HTML
<app-crypto-item
*ngFor="let item of cryptoItemGeneralDetails$ | async; index as i; even as e"
[cryptoItem]="item"
[index]="i"
[even]="e"
></app-crypto-item>
Notice the async
pipe in HTML is what actually subscribes to the observable: https://angular.io/api/common/AsyncPipe
Generally you want to wait until the last possible moment to subscribe. Otherwise you're juggling local variables and trying to pass them around, as you experienced.