For an Angular + ASP.NET Core Web API application, these are usually handled at two levels:
- Global/API-level errors → Angular HTTP interceptor.
- Form validation errors → ASP.NET Core returns
ValidationProblemDetails, and Angular maps theerrorsobject to form controls.
1. How would you globally handle API errors?
What is global API error handling?
Instead of writing catchError() in every Angular service:
this.http.get(...).pipe(
catchError(...)
);
we can create a global HTTP error interceptor.
It can centrally handle errors such as:
400 → Bad Request
401 → Unauthorized
403 → Forbidden
404 → Not Found
500 → Internal Server Error
The flow is:
Component
↓
Service
↓
HttpClient
↓
Error Interceptor
↓
ASP.NET Core API
↓
HTTP Error
↓
Error Interceptor
↓
Global handling
Simple Functional Error Interceptor
error-interceptor.ts
import { inject } from '@angular/core';
import {
HttpErrorResponse,
HttpInterceptorFn
} from '@angular/common/http';
import {
catchError,
throwError
} from 'rxjs';
import { Router } from '@angular/router';
export const errorInterceptor: HttpInterceptorFn =
(req, next) => {
const router = inject(Router);
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
switch (error.status) {
case 400:
console.error(
'Bad Request:',
error.error
);
break;
case 401:
console.error('Unauthorized');
router.navigate(['/login']);
break;
case 403:
console.error('Forbidden');
router.navigate(['/forbidden']);
break;
case 404:
console.error('Resource not found');
break;
case 500:
console.error(
'Server error:',
error.error
);
router.navigate(['/server-error']);
break;
default:
console.error(
'Unexpected API error:',
error
);
}
return throwError(() => error);
})
);
};
Register globally:
app.config.ts
import {
ApplicationConfig
} from '@angular/core';
import {
provideHttpClient,
withInterceptors
} from '@angular/common/http';
import {
jwtInterceptor
} from './interceptors/jwt-interceptor';
import {
errorInterceptor
} from './interceptors/error-interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([
jwtInterceptor,
errorInterceptor
])
)
]
};
Now every request made through HttpClient goes through these interceptors.
Handling Different Status Codes
400 — Bad Request
Usually represents invalid request data or validation problems.
case 400:
console.error(
'Bad request',
error.error
);
break;
For validation errors, however, we usually let the component access the actual validation response.
401 — Unauthorized
Example:
case 401:
router.navigate(['/login']);
break;
However, if your application supports refresh tokens, don't immediately redirect every 401 to login.
The authentication interceptor should first attempt:
401
↓
Try refresh token
↓
Refresh successful?
├── Yes → retry request
│
└── No → logout/login
403 — Forbidden
The user is authenticated but doesn't have sufficient authorization.
case 403:
router.navigate(['/forbidden']);
break;
For example:
User has valid JWT
↓
Role = User
↓
Calls Admin API
↓
403 Forbidden
404 — Not Found
case 404:
console.error(
'Requested resource was not found'
);
break;
500 — Internal Server Error
case 500:
console.error(
'Internal server error'
);
router.navigate(['/server-error']);
break;
The UI should normally display a user-friendly message rather than exposing exception details or stack traces.
ASP.NET Core Side
Angular's global handling becomes much cleaner when ASP.NET Core returns a consistent error format.
ASP.NET Core commonly uses ProblemDetails.
Example:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
"title": "An unexpected error occurred.",
"status": 500,
"detail": "Unable to process the request."
}
Angular can define:
export interface ApiProblem {
type?: string;
title?: string;
status?: number;
detail?: string;
}
Then:
const problem =
error.error as ApiProblem;
console.log(problem.title);
console.log(problem.detail);
2. How would you display ASP.NET Core validation errors in Angular?
This is slightly different from general API errors.
Suppose we have an ASP.NET Core DTO.
ASP.NET Core DTO
public class CreateProductDto
{
[Required]
public string Name { get; set; } = string.Empty;
[Range(1, 100000)]
public decimal Price { get; set; }
}
Controller:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpPost]
public IActionResult Create(
CreateProductDto product)
{
return Ok();
}
}
Because [ApiController] is used, ASP.NET Core automatically performs model validation.
Example Invalid Angular Request
Angular sends:
{
"name": "",
"price": 0
}
ASP.NET Core can return a 400 Bad Request containing validation errors similar to:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Name": [
"The Name field is required."
],
"Price": [
"The field Price must be between 1 and 100000."
]
}
}
The most important part for Angular is:
"errors": {
"Name": [
"The Name field is required."
],
"Price": [
"The field Price must be between 1 and 100000."
]
}
Angular Validation Error Interface
Create an interface corresponding to ASP.NET Core's validation response.
export interface ValidationProblem {
type?: string;
title?: string;
status?: number;
errors: {
[key: string]: string[];
};
}
The index signature:
[key: string]: string[];
means:
Name → string[]
Price → string[]
Email → string[]
etc.
Angular Reactive Form
Suppose we have:
productForm = new FormGroup({
name: new FormControl('', {
nonNullable: true
}),
price: new FormControl(0, {
nonNullable: true
})
});
Submit:
save(): void {
this.productService
.create(this.productForm.getRawValue())
.subscribe({
next: response => {
console.log('Product created');
},
error: error => {
if (error.status === 400) {
this.applyServerValidationErrors(
error.error.errors
);
}
}
});
}
Map ASP.NET Core Errors to Angular Controls
Create:
applyServerValidationErrors(
errors: Record<string, string[]>
): void {
Object.keys(errors).forEach(serverField => {
const controlName =
serverField.charAt(0).toLowerCase()
+ serverField.slice(1);
const control =
this.productForm.get(controlName);
if (control) {
control.setErrors({
...control.errors,
server: errors[serverField]
});
control.markAsTouched();
}
});
}
ASP.NET Core returns:
Name
Price
while your Angular form controls may be:
name
price
Therefore this example converts:
Name → name
Price → price
In larger applications, it's better to standardize field naming between the API contract and Angular rather than rely on ad-hoc case conversion.
Display Errors in HTML
product.html
<form [formGroup]="productForm"
(ngSubmit)="save()">
<div>
<label>Name</label>
<input
type="text"
formControlName="name">
@if (productForm.controls.name.errors?.['server']) {
@for (
error of productForm.controls.name.errors?.['server'];
track error
) {
<div class="error">
{{ error }}
</div>
}
}
</div>
<div>
<label>Price</label>
<input
type="number"
formControlName="price">
@if (productForm.controls.price.errors?.['server']) {
@for (
error of productForm.controls.price.errors?.['server'];
track error
) {
<div class="error">
{{ error }}
</div>
}
}
</div>
<button type="submit">
Save
</button>
</form>
Now if ASP.NET Core returns:
{
"errors": {
"Name": [
"The Name field is required."
]
}
}
Angular displays:
Name
[ ]
The Name field is required.
Client-Side + Server-Side Validation
In a real application, you normally use both.
Angular:
name: new FormControl('', [
Validators.required
])
provides immediate feedback.
ASP.NET Core:
[Required]
public string Name { get; set; }
provides authoritative server-side validation.
The flow becomes:
Angular Form
│
Client Validation
│
Valid locally
↓
POST /products
│
↓
ASP.NET Core
│
Server Validation
/ \
Valid Invalid
│ │
200 OK 400
│
ValidationProblemDetails
│
↓
Angular Form
│
setErrors(...)
│
↓
Display field errors
Angular validation improves UX. ASP.NET Core validation protects the API.
Never depend only on Angular validation because a client can bypass Angular completely and call the API directly.
Should the Global Interceptor Display Validation Errors?
Usually, not field-level validation errors.
The interceptor can handle general infrastructure/authentication errors:
401
403
404
500
But a component knows which form control corresponds to:
Name
Email
Price
So a useful separation is:
API Response
│
HTTP Error
│
┌─────────┴─────────┐
│ │
General Error Validation Error
│ │
Interceptor Component/Form
│ │
401/403/500 Name/Price/Email
│ │
Global handling setErrors(...)
You can still use a shared validation utility/service so every component doesn't duplicate the mapping logic.
Key Points
- Use an HTTP interceptor for global API error handling.
- Use
catchError()to inspectHttpErrorResponse. - Handle
401,403,404,500, etc. consistently. - Coordinate
401handling with your refresh-token interceptor. - ASP.NET Core should preferably return standardized
ProblemDetails/ValidationProblemDetails. [ApiController]automatically produces400responses for invalid model state in common controller scenarios.- ASP.NET Core validation errors are commonly returned through an
errorsdictionary. - Angular can map those server errors to reactive form controls using
setErrors(). - Client-side validation is for fast UX; server-side validation remains authoritative.
Interview Answers
Q1. How would you globally handle API errors in Angular?
I would create an HTTP interceptor and use catchError() to centrally process HttpErrorResponse. Based on the status code, I can handle 401, 403, 404, 500, logging, navigation, and user-friendly messages consistently across the application.
Q2. How would you display ASP.NET Core validation errors in Angular?
ASP.NET Core can return validation failures as ValidationProblemDetails, including an errors dictionary. Angular reads that dictionary, maps each server field to the corresponding reactive form control, calls setErrors(), and displays those messages next to the relevant fields.