These are common Angular + ASP.NET Core Web API scenario-based interview questions. Here are concise interview-ready answers.
1. How would you upload an image to ASP.NET Core?
In Angular, I would use FormData to send the image as multipart/form-data. ASP.NET Core receives it using IFormFile, validates the file, and saves it to storage such as local storage, Azure Blob Storage, or another file store.
uploadImage(file: File) {
const formData = new FormData();
formData.append('file', file);
return this.http.post(
`${environment.apiUrl}/api/images`,
formData
);
}
ASP.NET Core:
[HttpPost]
public async Task<IActionResult> Upload(IFormFile file)
{
if (file == null || file.Length == 0)
return BadRequest("File is required.");
var fileName = Path.GetFileName(file.FileName);
var path = Path.Combine("wwwroot/images", fileName);
await using var stream = new FileStream(path, FileMode.Create);
await file.CopyToAsync(stream);
return Ok(new { fileName });
}
Key point: Don't manually set Content-Type in Angular when using FormData; the browser generates the multipart boundary.
2. How would you implement a loading spinner for API calls?
For an application-wide spinner, I would use an HTTP interceptor. The interceptor increments a loading counter when a request starts and decrements it when the request completes using finalize().
export const loadingInterceptor: HttpInterceptorFn = (req, next) => {
const loading = inject(LoadingService);
loading.show();
return next(req).pipe(
finalize(() => loading.hide())
);
};
Register it:
provideHttpClient(
withInterceptors([loadingInterceptor])
)
A counter is preferable to a simple boolean because several HTTP requests can execute simultaneously.
Interview answer:
I use an HTTP interceptor for a global loading spinner so individual components don't need to manage loading state for every API call.
3. How would you handle an API that is temporarily unavailable?
I would handle temporary failures using RxJS retry logic, centralized error handling, and a user-friendly message. For transient failures such as 503 Service Unavailable, retrying with a delay or exponential backoff can be appropriate.
this.http.get<Product[]>('/api/products').pipe(
retry({
count: 3,
delay: 2000
}),
catchError(error => {
console.error(error);
return throwError(() => error);
})
);
For production systems, I would generally not blindly retry every error:
503,502, network errors → potentially retry400→ don't retry401→ authentication/refresh-token handling403→ don't retry404→ usually don't retry
The ASP.NET Core side can additionally use resilience mechanisms such as timeouts and circuit breakers when it calls downstream services.
4. How would you avoid duplicate API requests?
It depends on why the duplicates occur. For multiple Angular subscribers requesting the same data, I can share the HTTP result using shareReplay().
private products$?: Observable<Product[]>;
getProducts(): Observable<Product[]> {
if (!this.products$) {
this.products$ = this.http
.get<Product[]>('/api/products')
.pipe(
shareReplay(1)
);
}
return this.products$;
}
Now multiple subscribers can reuse the result instead of generating another HTTP request.
For search boxes, I would commonly use:
searchControl.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(search =>
this.http.get<Product[]>(
`/api/products?search=${search}`
)
)
);
Here:
debounceTime() → reduces excessive requestsdistinctUntilChanged() → prevents repeated identical searchesswitchMap() → cancels the previous request when a newer search arrives.
For duplicate POST/payment/order operations, frontend deduplication alone isn't sufficient; the backend should support idempotency.
5. How would you cache API data on the Angular side?
For simple in-memory caching, I can store the API result in a service using shareReplay().
@Injectable({
providedIn: 'root'
})
export class ProductService {
private products$?: Observable<Product[]>;
constructor(private http: HttpClient) {}
getProducts(): Observable<Product[]> {
if (!this.products$) {
this.products$ = this.http
.get<Product[]>('/api/products')
.pipe(
shareReplay(1)
);
}
return this.products$;
}
clearCache() {
this.products$ = undefined;
}
}
First call:
Component
↓
ProductService
↓
ASP.NET Core API
↓
shareReplay()
↓
Cache
Subsequent calls:
Component
↓
ProductService
↓
Cached result
For different requirements, I could use:
| Requirement | Approach |
|---|---|
| Simple API cache | shareReplay() |
| Application state | Signals / RxJS service |
| Complex global state | NgRx |
| Persist across refresh | sessionStorage / localStorage where appropriate |
| Structured offline/client database | IndexedDB |
| Server-side caching | ASP.NET Core IMemoryCache / distributed cache such as Redis |
Important: Client-side caching should have an invalidation strategy. After creating, updating, or deleting a product, for example, clear or refresh the cached product data.
Quick interview summary
Image upload:
FormData→ ASP.NET CoreIFormFile.
Loading spinner: HTTP interceptor + loading service +finalize().
Temporary API failure: selective retry/backoff + centralized error handling.
Duplicate requests:shareReplay(),debounceTime(),distinctUntilChanged(), andswitchMap()depending on the scenario.
Angular caching: service-level cache usingshareReplay()or Signals/RxJS state, with proper cache invalidation.