Handling long-running API calls
Long running API calls can affect your application rendering time, that might lead to the SEO disaster. In this lesson you will learn how you can protect your application against such situation.
The Nightmare of Long-Running HTTP Calls#
Angular Universal gives your application the advantage of performing HTTP calls on the server. On the other hand, performing an HTTP call takes time. What if a call is long-running? You can simulate this behavior by slowing down one of the endpoints in terrain-shop-service
. Introduce a setTimeout
to the /promotions endpoint to delay the response:
const express = require('express');
const cors = require('cors');
const app = express();
const port = process.env.PORT || 8080;
app.use(
cors({
origin: true,
credentials: true,
})
);
app.get('/opening', (req, res) => {
console.log('GET opening');
res.send({ open: '8 AM', close: '10 PM' });
});
app.get('/promotions', (req, res) => {
console.log('GET promotions');
setTimeout(() => {
res.send(['Buy four potatoes and pay for three!']);
}, 4000);
});
app.listen(port, () => {
console.log(
`Backend is runing on: http://localhost:${port}`
);
});
Use Postman to check how long it now takes to render the http://localhost:4200/terrain-shop landing page:

An equivalent test can be performed using cURL:
curl -o /dev/null -w %{time_total} http://localhost:4200/terrain-shop
Your application's rendering process is as fast as the slowest API call!
Introducing API watchdog#
To protect yourself against such situations, you can set up a route resolver that allows you to get data before navigating to a new route.
Thanks to the isPlatformServer()
and isPlatformBrowser()
methods, you can determine the current runtime of the Angular application and modify the logic of the resolver accordingly.
Generate a new route resolver service:
ng g s terrain-shop-resolver --skipTests
Copy the following code to src/app/terrain-shop-resolver.service.ts:
import {
Injectable,
Inject,
PLATFORM_ID,
} from '@angular/core';
import { Resolve } from '@angular/router';
import { TerrainShopService } from './terrain-shop.service';
import { Observable, timer, race } from 'rxjs';
import { isPlatformServer } from '@angular/common';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class TerrainShopResolverService
implements Resolve<string[]> {
constructor(
private ts: TerrainShopService,
@Inject(PLATFORM_ID) private platformId: any
) {}
public resolve(): Observable<string[]> {
if (isPlatformServer(this.platformId)) {
return race(
this.ts.getPromotions(),
timer(500).pipe(map(() => ['Loading data...']))
);
} else {
return this.ts.getPromotions();
}
}
}
The code above introduces the TerrainShopResolverService
class that implements the Resolve
interface. To fulfill its interface contract, TerrainShopResolverService
implements the resolve()
method. Inside this method, you check the current runtime of the application.
This page is a preview of The newline Guide to Angular Universal