Section 6: Day 4 section 6
- Angular routing
- Adding toasts
- Using Angular route guards
- App initialization
- Route animations
Section 7: Error handing
Learning goals:
- API middleware
- Angular interceptors
- Troubleshooting exceptions
Creating error controller for testing
Using class in controller:
public class BuggyController : BaseApiController
{
[HttpGet("auth")]
public IActionResult GetAuth()
{
return Unauthorized();
}
[HttpPatch("not-found")]
public IActionResult GetNotFound()
{
return NotFound();
}
[HttpGet("server-error")]
public IActionResult GetServerError()
{
throw new Exception("This is a server error");
}
[HttpGet("bad-request")]
public IActionResult GetBadRequest()
{
return BadRequest("This is a bad request");
}
}
Then, Tesing API with Postman
this doesn’t recognize and these code also need to have configuration in Program.cs , although this line doesn’t appear, but it may be relevant in diiferent file
app.UseDeveloperExceptionPage();
try changing mode “Developer” to see error “Production”
ASP.NET Core Middleware | Microsoft Learn
Using ILogger to write console log
Middleware and Statecode
namespace API.Errors;
public class ApiException(int StatusCode, string Message, string? Details)
{
public int StatusCode { get; set; } = StatusCode;
public string? Message { get; set; } = Message;
public string? Details { get; set; } = Details;
}
namespace API.Middleware;
public class ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger, IHostEnvironment env)
{
public async Task InvokeAsync(HttpContext context)
{
try
{
await next(context);
}
catch (Exception ex)
{
logger.LogError(ex, "{Message}", ex.Message);
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var respone = env.IsDevelopment()
? new ApiException(context.Response.StatusCode, ex.Message, ex.StackTrace)
: new ApiException(context.Response.StatusCode, ex.Message, "Internal Server Error");
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
var json = JsonSerializer.Serialize(respone, options);
await context.Response.WriteAsync(json);
}
}
}
Program.cs
app.UseMiddleware<ExceptionMiddleware>();
Creates component for tesing errors
ng g c features/test-errors
import { HttpClient } from '@angular/common/http';
import { Component, inject } from '@angular/core';
@Component({
selector: 'app-test-errors',
imports: [],
templateUrl: './test-errors.html',
styleUrl: './test-errors.css'
})
export class TestErrors {
private http = inject(HttpClient);
baseUrl = 'https://localhost:5001/api/';
get404Error() {
return this.http.get(`${this.baseUrl}buggy/not-found`).subscribe({
next: (response) => console.log(response),
error: (error) => console.log(error)
});
}
get400Error() {
return this.http.get(`${this.baseUrl}buggy/bad-request`).subscribe({
next: (response) => console.log(response),
error: (error) => console.log(error)
});
}
get500Error() {
return this.http.get(`${this.baseUrl}buggy/server-error`).subscribe({
next: (response) => console.log(response),
error: (error) => console.log(error)
});
}
get401Error() {
return this.http.get(`${this.baseUrl}buggy/auth`).subscribe({
next: (response) => console.log(response),
error: (error) => console.log(error)
});
}
get400ValidationError() {
return this.http.post(`${this.baseUrl}account/register`, {}).subscribe({
next: (response) => console.log(response),
error: (error) => console.log(error)
});
}
}
Components testing errors
<div class="flex justify-center gap-3 pt-10">
<button class="btn btn-neutral" (click)="get500Error()">Get 500 Error</button>
<button class="btn btn-accent" (click)="get400Error()">Get 400 Error</button>
<button class="btn btn-secondary" (click)="get401Error()">Get 401 Error</button>
<button class="btn btn-success" (click)="get404Error()">Get 404 Error</button>
<button class="btn btn-info" (click)="get400ValidationError()">Get 400 Validation Error</button>
</div>
⇒ adding in app.routes and adding nav bar components
Interceptor error with angular
ng g interceptor error
"@schematics/angular:interceptor": {
"skipTests": true,
"path": "src/core/interceptors"
}
import { HttpInterceptorFn } from '@angular/common/http';
import { catchError, throwError } from 'rxjs';
import { ToastService } from '../services/toast-service';
import { Router } from '@angular/router';
import { inject } from '@angular/core/primitives/di';
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
const toast = inject(ToastService);
const router = inject(Router);
return next(req).pipe(
catchError((error) => {
if(error)
{
switch(error.status) {
case 400:
toast.error(error.error);
break;
case 401:
toast.error('Unauthorized');
break;
case 404:
toast.error('Not Found');
break;
case 500:
toast.error('Server Error');
break;
default:
toast.error('Something went wrong');
break;
}
}
// return throwError(() => error);
throw error;
})
);
};
app.config.ts
provideHttpClient(withInterceptors([errorInterceptor])),
@if (validationErrors().length > 0) {
<div class="card bg-base-100 flex mt-5 rounded-2xl w-1/2 p-3 mx-auto">
<ul class="flex flex-col text-red-600 space-y-2">
@for (error of validationErrors(); track $index) {
<li>{{error}}</li>
}
</ul>
</div>
}
ng g c shared/errors/not-found
ng g c shared/errors/server-erro
Adding not-found page
<div class="card bg-base-100 gap-3 w-4xl mx-auto flex flex-col items-center rounded-xl shadow-xl p-10">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="size-32 text-error">
<path stroke-linecap="round" stroke-linejoin="round"
d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
</svg>
<h1 class="card-title text-4xl justify-center">Not found</h1>
<p class="card-body text-xl text-center">Sory, what you are looking for cannot be found</p>
<button class="btn btn-primary" (click)="goBack()">Go back</button>
</div>
{path: '**', component: NotFound},
import { Component, inject } from '@angular/core';
import { Location } from '@angular/common';
@Component({
selector: 'app-not-found',
imports: [],
templateUrl: './not-found.html',
styleUrl: './not-found.css'
})
export class NotFound {
private location = inject(Location);
goBack() {
this.location.back();
}
}
Adding server-error page
Component
import { Component, inject, signal } from '@angular/core';
import { Router } from '@angular/router';
import { ApiError } from '../../../types/error';
@Component({
selector: 'app-server-error',
imports: [],
templateUrl: './server-error.html',
styleUrl: './server-error.css'
})
export class ServerError {
protected error : ApiError;
private router = inject(Router);
protected showDetails = false;
constructor() {
const navigation = this.router.getCurrentNavigation();
this.error = navigation?.extras?.state?.['error'];
}
detailsToggle() {
this.showDetails = !this.showDetails;
}
}
<div class="card bg-base-100 gap-3 w-4xl mx-auto flex flex-col items-center rounded-xl shadow-xl p-10">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-32 text-error">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 12.75c1.148 0 2.278.08 3.383.237 1.037.146 1.866.966 1.866 2.013 0 3.728-2.35 6.75-5.25 6.75S6.75 18.728 6.75 15c0-1.046.83-1.867 1.866-2.013A24.204 24.204 0 0 1 12 12.75Zm0 0c2.883 0 5.647.508 8.207 1.44a23.91 23.91 0 0 1-1.152 6.06M12 12.75c-2.883 0-5.647.508-8.208 1.44.125 2.104.52 4.136 1.153 6.06M12 12.75a2.25 2.25 0 0 0 2.248-2.354M12 12.75a2.25 2.25 0 0 1-2.248-2.354M12 8.25c.995 0 1.971-.08 2.922-.236.403-.066.74-.358.795-.762a3.778 3.778 0 0 0-.399-2.25M12 8.25c-.995 0-1.97-.08-2.922-.236-.402-.066-.74-.358-.795-.762a3.734 3.734 0 0 1 .4-2.253M12 8.25a2.25 2.25 0 0 0-2.248 2.146M12 8.25a2.25 2.25 0 0 1 2.248 2.146M8.683 5a6.032 6.032 0 0 1-1.155-1.002c.07-.63.27-1.222.574-1.747m.581 2.749A3.75 3.75 0 0 1 15.318 5m0 0c.427-.283.815-.62 1.155-.999a4.471 4.471 0 0 0-.575-1.752M4.921 6a24.048 24.048 0 0 0-.392 3.314c1.668.546 3.416.914 5.223 1.082M19.08 6c.205 1.08.337 2.187.392 3.314a23.882 23.882 0 0 1-5.223 1.082" />
</svg>
<h1 class="card-title text-4xl justify-center">Server error</h1>
<p class="card-body text-xl text-center">{{error.message}}</p>
<button class="btn btn-primary" (click)="detailsToggle()">Details</button>
@if (showDetails) {
<div>{{error.details}}</div>
}
</div>
app.route.ts
{path: 'server-error', component: ServerError},