1---2title: "Why your Angular dashboard makes too many API calls"3slug: "fewer-api-calls-angular-dashboards"4date: "2026-10-04"5description: "Four patterns I look for when a data-heavy Angular screen feels slow, and how to measure the difference before you change anything."6cover: chart7tags:8 - Angular9 - Performance10 - RxJS11---12 13Most slow dashboards I have worked on were not slow because of the grid, the framework, or the server. They were slow because the frontend asked for the same data again and again, often several times a second.14 15On one enterprise Angular app, caching and tighter state management cut our API calls by 40%. The backend did not change at all.16 17<Stat value="40%" label="fewer API calls" detail="On a live enterprise Angular app, from caching and better state management. No backend changes." />18 19Here are the four patterns I now check first on any data-heavy screen.20 21## Start by counting22 23Before changing anything, find out what the screen actually sends. The Network tab works, but a tiny interceptor gives you a running count per endpoint in the console:24 25```ts26export const requestCounter: HttpInterceptorFn = (req, next) => {27 console.count(`${req.method} ${req.url}`);28 return next(req);29};30```31 32Register it with `provideHttpClient(withInterceptors([requestCounter]))`, click through the screen like a real user, and read the numbers. Any endpoint with a count higher than you expected is where to start.33 34## 1. Filters that fire on every keystroke35 36A search box wired straight to the API sends one request per letter. Two RxJS operators fix most of it: `debounceTime` waits until the user pauses, and `distinctUntilChanged` skips a search that matches the previous one.37 38```ts39readonly query = signal('');40 41readonly results = toSignal(42 toObservable(this.query).pipe(43 debounceTime(300),44 distinctUntilChanged(),45 switchMap((q) => this.api.search(q)),46 ),47 { initialValue: [] },48);49```50 51`switchMap` also cancels a request that is still in flight when a newer search starts, so an old, slow response can never overwrite a newer one.52 53<BarChart54 title="Requests sent while typing “invoice” into a filter"55 caption="Worked example, not a measurement. The user pauses after “inv” and again after “invoice”, then quickly deletes the last letter and types it again."56 valueLabel="Requests"57>58 <Bar label="Every keystroke" value="9" />59 <Bar label="debounceTime(300)" value="3" />60 <Bar label="+ distinctUntilChanged" value="2" highlight="true" />61</BarChart>62 63## 2. The same data, requested twice at once64 65Status lists, user lookups and dropdown options are often needed by several components on the same screen. If each one calls the service, each one sends its own request.66 67Share one request instead:68 69```ts70private readonly statuses$ = this.http71 .get<Status[]>('/api/statuses')72 .pipe(shareReplay({ bufferSize: 1, refCount: false }));73 74getStatuses() {75 return this.statuses$;76}77```78 79Every subscriber now gets the same response, and only the first one triggers the call. This works well for reference data that rarely changes during a session.80 81> If two components need the same data, the second one should never have to ask the server for it.82 83## 3. Navigation that refetches unchanged data84 85Going from a list to a record and back usually reloads the whole list. For data that changes slowly, keep it in a store and only reload it when it is old or when you know it has changed.86 87```ts88private readonly maxAgeMs = 60_000;89private loadedAt = 0;90 91loadCases() {92 if (Date.now() - this.loadedAt < this.maxAgeMs) return;93 this.loadedAt = Date.now();94 this.api.getCases().subscribe((cases) => this.cases.set(cases));95}96 97invalidate() {98 this.loadedAt = 0;99}100```101 102Call `invalidate()` after a save, so the next visit loads fresh data. Users get instant navigation without ever seeing stale records they just edited.103 104## 4. Grids that ask for more than they show105 106AG Grid's server-side row model loads rows in blocks. The default block size is 100 rows, so a grid showing 25 rows per page can fetch four times more data than anyone sees.107 108Two settings help:109 110- Set `cacheBlockSize` to match your page size, so each request returns exactly one page.111- Set `blockLoadDebounceMillis` to a small value, so fast scrolling does not fire a request for every block the user scrolls past.112 113---114 115## What to take away116 117None of this is clever. It is the boring part of frontend work: count the requests, find the repeats, and remove the ones nobody needed.118 119If your dashboard feels slow, add the counter first. The numbers usually point straight at the problem, and the fix is often a few lines.