In an Angular 18 app using the new template control-flow syntax, how do I use an async pipe to display a loading status when new data it fetched, and how do I make it display errors?
Currently I have a component that watches for changes to route parameters and fetches data based on that, which works well.
@Component({
selector: 'app-page-demo',
standalone: true,
imports: [AsyncPipe, JsonPipe],
templateUrl: './page-demo.component.html',
})
export class PageDemoComponent implements OnChanges {
private readonly dataService = inject(DataService);
private readonly activatedRoute = inject(ActivatedRoute);
errorMessage = '';
readonly timesheet$ = this.activatedRoute.paramMap.pipe(
switchMap((paramMap) => {
const date = paramMap.get('date');
return this.dataServicve.getData$(date);
}),
catchError((err) => {
this.errorMessage = err instanceof Error ? err.message : 'Unable to fetch data!';
return of(undefined);
}),
shareReplay({ refCount: true, bufferSize: 1 }),
);
}
@if (errorMessage === '') {
<a routerLink="page/2024-09-21">last week</a> | <a routerLink="page/2024-10-05">next week</a>
}
@if (data$ | async; as data) {
@if (errorMessage === '') {
<h1>Data loaded!</h1>
<pre>{{ data | json }}</pre>
} @else {
There was an error: {{ errorMessage }}
}
} @else {
Loading...
}
The problem I am having is that when I click on one of the links to navigate to the same route with with a different parameter, the "Loading..." is not displayed while the data is loading as I would expect it to be. It only displays on the initial data load.
Also, I feel like I am not handling errors properly here. This mostly works, but it feels kind of wrong to me and I don't know a better way to do this.
I think you need to add a seperate obs which tracks loading:
errorMessage = '';
data$ = this.activatedRoute.paramMap.pipe(
switchMap((paramMap) => {
const date = paramMap.get('date');
return this.dataServicve.getData$(date);
}),
catchError(err => {
this.errorMessage = err instanceof Error ? err.message : 'Unable to fetch data!';
// clear the result of previous getData stored by shareReplay
// also serve the purpose of informing that getData has completed, albeit with error
return of(null);
}),
shareReplay({ refCount: true, bufferSize: 1 }),
);
// starts loading on param change, stop loading when data$ emits
loading$ = merge(
this.activatedRoute.paramMap.pipe(map(() => true),),
this.data$.pipe(map(() => false)),
);
then refactor ur html like so.
eventho the data$ was getting subscribed late, it should still store correct value due to shareReplay
@if(loading$|async){
Loading...
} @else {
@if (data$ | async; as data) {
<h1>Data loaded!</h1>
<pre>{{ data | json }}</pre>
} @else {
There was an error: {{ errorMessage }}
}
}
I did not test the code above, so improvise if there's mistake on my part