Reputation: 2645
I'm aware of the number of questions with similar titles/issues: I've spent the past day and a half reading and trying out possible solutions with no luck. My situation is a bit different and my version of Angular is newer.
I have a page with a chart (chart.js through ng2-charts), and I want to paginate its data. This is how it looks including the controls:
<div class="paginable-area" [class.loading]="chartLoading">
<app-line-chart *ngIf="!tableError" [lineChartData]="chartData" [lineChartLabels]="chartLabels"></app-line-chart>
<div class="page-controls">
<button class="button" (click)="previousChartPage()" [disabled]="currentChartPage <= 1">◀</button>
<button class="button" (click)="nextChartPage()">▶</button>
</div>
</div>
When the page loads, the first page is shown correctly and looks good. But as soon as I press the button to get a different page, I get the following error in the console:
TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
RxJS 13
Angular 16
RxJS 30
getAggregates dashboard.component.ts:144
nextChartPage dashboard.component.ts:92
DashboardComponent_Template_button_click_9_listener dashboard.component.html:10
Angular 22
DashboardComponent_Template dashboard.component.html:10
Angular 13
RxJS 3
Pressing the next page button, these are the methods that get called, in order:
public nextChartPage() : void {
this.currentChartPage += 1;
this.getAggregates();
}
// ...
private getAggregates() : void {
this.chartLoading = true;
this.statsService.getAggregateStats(this.currentChartPage)
.pipe(
toArray(),
catchError((error) => {
this.chartError = `An error occurred: ${error.toString()}`;
return of(error);
}),
map(aggregates => {
const data = {
labels: [] as string[],
successful: [] as number[],
unsuccessful: [] as number[],
};
aggregates.forEach((agg: DataAggregate) => {
data.labels.push(agg.date);
data.successful.push(agg.successful);
data.unsuccessful.push(agg.unsuccessful);
});
this.chartLabels = data.labels;
if (typeof this.chartData[0].data !== "undefined" && typeof this.chartData[1].data !== "undefined") {
this.chartData[0].data = data.successful;
this.chartData[1].data = data.unsuccessful;
}
this.chartLoading = false;
return data;
})
)
.subscribe();
}
// ...
// statsService.getAggregateStats, the function that makes the HTTP GET request :
public getAggregateStats(page: number = 1) : Observable<DataAggregate> {
return this.http.get<DataAggregateResponseItem[]>(
`https://my-api.xxx/stats?page=${page}`,
).pipe(
mergeAll(),
map((r: DataAggregateResponseItem) => {
const thing: DataAggregate = {
date: r.day,
successful: parseInt(r.successful),
unsuccessful: parseInt(r.unsuccessful),
};
console.log(`${JSON.stringify(thing)}`);
return thing;
}),
);
}
As you can see, the getAggregates()
method is called each time a chart page is requested, including when the chart first loads. The first call succeeds, but the second one shows the error (I also tried to increment the page counter and call it again on page load instead of waiting for the button to be pressed: same outcome), and any subsequent calls simply end up with an empty object.
I have a table with data from the same api, paginated the exact same way, on the same page, but it works flawlessly. The data of course has a different shape, so the issue is doubtless somewhere in a map(), but I just can't figure it out.
of()
, but that just causes stuff to come out as Observables of Observables...I'm still rather new to Angular and rxjs so it's very likely that I'm doing something wrong, but I have no idea what. Please help.
EDIT: After changing the service method (the last one shown in the code above) to this:
public getAggregateStats(page: number = 1) : Observable<DataAggregate> {
return this.http.get<DataAggregateResponseItem[]>(
`https://my-api.xxx/stats?page=${page}`,
).pipe(
map((r: DataAggregateResponseItem[]) => {
return r.map((agg) => {
return {
date: agg.day,
successful: parseInt(agg.successful),
unsuccessful: parseInt(agg.unsuccessful),
} as DataAggregate;
});
}),
mergeAll(),
);
}
something changed: when I load the next page of the chart, the error is:
TypeError: r.map is not a function
getAggregateStats stats.service.ts:75
RxJS 9
Angular 16
RxJS 28
getAggregates dashboard.component.ts:143
nextChartPage dashboard.component.ts:92
DashboardComponent_Template_button_click_9_listener dashboard.component.html:10
Angular 22
DashboardComponent_Template dashboard.component.html:10
Angular 13
RxJS 3
Which makes me think that the pipe is initially receiving the data as expected, but when the method is called again, it instead gets something that is not an array (hence why it doesn't have a map method). But if I look in the console I can clearly see that the second request is succeeding and even contains the correct content!
// First request, when the page loads:
GET https://my-api.xxx/stats?page=1 [HTTP/1.1 200 OK 249ms]
[{"successful":"1","unsuccessful":"1","day":"2020-12-15"},{"successful":"1","unsuccessful":"0","day":"2020-12-14"}]
// Second request, when I request the next page
GET https://my-api.xxx/stats?page=2 [HTTP/1.1 200 OK 249ms]
{"2":{"successful":"2","unsuccessful":"1","day":"2020-12-11"},"3":{"successful":"3","unsuccessful":"6","day":"2020-12-10"}}
Upvotes: 1
Views: 8101
Reputation: 112
For me, the issue was that the http api where the get requests are going was failed.
Upvotes: 0
Reputation: 2645
I just figured it out, and in the end the issue had nothing to do with Angular or Typescript at all (although the error message was not exactly useful to pinpoint the cause).
The API I'm getting the results from is made with Laravel (PHP), and it turns out that although the first page is returned in the following form:
[
"0" => ["some" => "data"],
"1" => ["some" => "more data"],
]
which is translated to JSON as:
[
{"some": "data"},
{"some": "more data"}
]
The second page, and subsequent ones, on the PHP side look like this:
[
"2" => ["some" => "data"],
"3" => ["some" => "more data"],
]
and on the surface that seems in order: the indexes are going up because subsequent items are being returned. But this led Laravel's whatchamacallit it uses to convert array into JSON to interpret the previous thusly:
{
"2": {"some": "data"},
"3": {"some": "more data"}
}
Which of course threw Angular off completely.
I apologise to anyone who had to read all this nonsense. I, at least, have learned my lesson: never trust PHP.
Upvotes: 2
Reputation: 1245
I faced this kind of issues many times and ngIf
saved me. Pass data to chart when you have it.
<app-line-chart *ngIf="!tableError && chartData.length > 0" [lineChartData]="chartData" [lineChartLabels]="chartLabels"></app-line-chart>
Upvotes: 0