Skip to content

Commit 85fb059

Browse files
committed
http interceptors
1 parent f5d84e4 commit 85fb059

File tree

9 files changed

+245
-21
lines changed

9 files changed

+245
-21
lines changed

README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,66 @@ To configure the pre-commit hook, simply add a `precommit` npm script. We want t
248248
- [GitHub Actions for Angular](https://github.com/rodrigokamada/angular-github-actions)
249249
- [Angular 16 - milestone release](https://github.com/actionanand/ng16-signal-milestone-release)
250250
- [Colorful Console Message](https://www.samanthaming.com/tidbits/40-colorful-console-message/)
251+
252+
## Wiki
253+
254+
### What are Angular interceptors?
255+
256+
Interceptors are unique Angular services that we can implement to add behavior to HTTP requests in our application. **HttpInterceptor** provides a way to intercept **HTTP requests** and **responses**. In this sense, each interceptor can handle the request entirely by itself.
257+
258+
As the diagram above shows, the interceptors are always in the middle of an HTTP request. As middlemen, they allow us to perform operations on the requests on their way to and back from the server, making it a perfect place to centralize code for things like adding headers, passing tokens, caching, and error handling.
259+
260+
1. [Angular Error Interceptor](https://dev.to/this-is-angular/angular-error-interceptor-12bg)
261+
2. [Transitioning from retryWhen to retry with Delay in RxJS](https://medium.com/@nambi.n.rajan/transitioning-from-retrywhen-to-retry-with-delay-in-rxjs-699ab6a5c7ee)
262+
3. [Retry Error HTTP Requests in Angular (without retryWhen)](https://blog.adnanhalilovic.com/2022/07/31/retry-error-http-requests-in-angular-without-retrywhen/)
263+
4. [Angular 17: HTTP Interceptors guide](https://freedium.cfd/https://medium.com/@mohsinogen/angular-17-http-interceptors-guide-417e7c8ffada)
264+
5. [mergeMap – RxJS Reference](https://angular.love/mergemap-rxjs-reference)
265+
266+
```ts
267+
// The Old Way: `retryWhen`
268+
import { throwError, retryWhen, delay, tap, scan } from 'rxjs';
269+
270+
function usingRetryWhen(duration, maxtries) {
271+
return retryWhen(errors =>
272+
errors.pipe(
273+
scan((count, err) => {
274+
if (count >= maxtries) {
275+
throw err;
276+
}
277+
return count + 1;
278+
}, 0),
279+
tap(val => console.log(`#${val} Attemp with RetryWhen`)),
280+
delay(duration),
281+
),
282+
);
283+
}
284+
285+
let triggerErrorWithRetryWhen = throwError(() => new Error('Test Error'));
286+
287+
triggerErrorWithRetryWhen.pipe(usingRetryWhen(2000, 2)).subscribe({
288+
next: val => console.log(val, 'from next'),
289+
error: val => console.log(val, 'from error'),
290+
});
291+
```
292+
293+
```ts
294+
// The New Way: `retry` with `delay`
295+
import { throwError, retry, timer } from 'rxjs';
296+
297+
function retryWithDelay(duration, maxTries) {
298+
return retry({
299+
count: maxTries,
300+
delay: (error, retryCount) => {
301+
console.log(`Retry attempt #${retryCount}`);
302+
return timer(duration);
303+
},
304+
});
305+
}
306+
307+
let triggerErrorWithRetry = throwError(() => new Error('Test Error'));
308+
309+
triggerErrorWithRetry.pipe(retryWithDelay(2000, 2)).subscribe({
310+
next: val => console.log(val, 'from next'),
311+
error: val => console.log(val, 'from error'),
312+
});
313+
```

backend/data/user-places.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,6 @@
99
"lat": 27.1751,
1010
"lon": 78.0421
1111
},
12-
{
13-
"id": "p18",
14-
"title": "Amazon River",
15-
"image": {
16-
"src": "amazon-river.jpg",
17-
"alt": "Navigating the waters of the Amazon River, surrounded by dense rainforest."
18-
},
19-
"lat": -3.4653,
20-
"lon": -58.38
21-
},
2212
{
2313
"id": "p9",
2414
"title": "Great Barrier Reef",
@@ -28,5 +18,15 @@
2818
},
2919
"lat": -18.2871,
3020
"lon": 147.6992
21+
},
22+
{
23+
"id": "p18",
24+
"title": "Amazon River",
25+
"image": {
26+
"src": "amazon-river.jpg",
27+
"alt": "Navigating the waters of the Amazon River, surrounded by dense rainforest."
28+
},
29+
"lat": -3.4653,
30+
"lon": -58.38
3131
}
3232
]

proxy.conf.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// var defaultTarget = 'http://localhost:3000';
2-
var defaultTarget = 'https://3000-actionanand-angularhttp-six5y8k89a8.ws-us116.gitpod.io';
1+
var defaultTarget = 'http://localhost:3000';
2+
// var defaultTarget = 'https://3000-actionanand-angularhttp-six5y8k89a8.ws-us116.gitpod.io';
33

44
module.exports = [
55
{

src/app/app.config.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
22
import { provideRouter } from '@angular/router';
3+
import { provideHttpClient, withInterceptors } from '@angular/common/http';
34

45
import { routes } from './app.routes';
6+
import { LoggingInterceptor } from './services/interceptors/logging.interceptor';
7+
import { RetryInterceptor } from './services/interceptors/retry.interceptor';
58

69
export const appConfig: ApplicationConfig = {
7-
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
10+
providers: [
11+
provideZoneChangeDetection({ eventCoalescing: true }),
12+
provideRouter(routes),
13+
provideHttpClient(withInterceptors([LoggingInterceptor, RetryInterceptor])),
14+
],
815
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { HttpErrorResponse, HttpHandlerFn, HttpRequest } from '@angular/common/http';
3+
import { catchError, throwError } from 'rxjs';
4+
5+
export const ErrorInterceptor = (req: HttpRequest<unknown>, next: HttpHandlerFn) => {
6+
return next(req).pipe(
7+
catchError((err: any) => {
8+
if (err instanceof HttpErrorResponse) {
9+
// Handle HTTP errors
10+
if (err.status == 401) {
11+
// Specific handling for unauthorized errors
12+
console.error('Unauthorized request:', err);
13+
// You might trigger a re-authentication flow or redirect the user here
14+
} else {
15+
// Handle other HTTP error codes
16+
console.error('HTTP error:', err);
17+
}
18+
} else {
19+
// Handle non-HTTP errors
20+
console.error('An error occurred:', err);
21+
}
22+
23+
// Re-throw the error to propagate it further
24+
return throwError(() => err);
25+
}),
26+
);
27+
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http';
2+
3+
/*
4+
// DI based / class based interceptors
5+
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
6+
import { Injectable } from '@angular/core';
7+
8+
import { Observable } from 'rxjs';
9+
10+
@Injectable()
11+
export class LoggingInterceptor implements HttpInterceptor {
12+
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
13+
console.log('Interceptor: ', req);
14+
return next.handle(req);
15+
}
16+
}
17+
18+
// use the below code in 'AppModule'
19+
@NgModule({
20+
providers: [
21+
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true }
22+
]
23+
})
24+
export class AppModule {}
25+
/*
26+
27+
28+
/*
29+
export function LoggingInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn) {
30+
console.log('Interceptor: ', req);
31+
return next(req);
32+
}
33+
*/
34+
35+
export const LoggingInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, next: HttpHandlerFn) => {
36+
const newReq = req.clone({
37+
headers: req.headers.set('new-Header', 'headerValue'),
38+
});
39+
40+
// const newReq = req.clone({
41+
// setHeaders: {
42+
// 'new-Header': 'headerValue'
43+
// }
44+
// });
45+
46+
console.log('Interceptor: ', newReq);
47+
return next(newReq);
48+
};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { HttpErrorResponse, HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http';
2+
import { retry, timer } from 'rxjs';
3+
// import { mergeMap, retryWhen, of } from 'rxjs';
4+
5+
const maxRetries = 3;
6+
const delayMs = 2000;
7+
8+
export const RetryInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, next: HttpHandlerFn) => {
9+
return next(req).pipe(
10+
retry({
11+
count: maxRetries,
12+
delay: (error: HttpErrorResponse, retryCount) => {
13+
if (error.status == 401) {
14+
console.log(req.url, ' : 401 retrying...', retryCount);
15+
return timer(delayMs);
16+
} else if (error.status >= 500) {
17+
console.log(req.url, ' : 500 retrying...', retryCount);
18+
return timer(delayMs);
19+
} else {
20+
console.log(`${req.url} : ${error.status} retrying... ${retryCount}`);
21+
return timer(delayMs);
22+
}
23+
throw error;
24+
},
25+
}),
26+
);
27+
};
28+
29+
/*
30+
// Simple retry
31+
export const RetryInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, next: HttpHandlerFn) => {
32+
return next(req).pipe(
33+
retry(maxRetries)
34+
);
35+
}
36+
*/
37+
38+
/*
39+
// using 'retryWhen' for status based retry
40+
export const RetryInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, next: HttpHandlerFn) => {
41+
return next(req).pipe(
42+
retryWhen((error) => {
43+
return error.pipe(
44+
mergeMap((err, index) => {
45+
if (err.status == 401 && index < maxRetries) {
46+
return of(err).pipe(
47+
delay(delayMs)
48+
)
49+
}
50+
throw err;
51+
})
52+
)
53+
}
54+
)
55+
);
56+
}
57+
*/
58+
59+
/*
60+
// using 'retryWhen' generally
61+
export const RetryInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, next: HttpHandlerFn) => {
62+
return next(req).pipe(
63+
retryWhen((error) => {
64+
return error.pipe(
65+
scan((retryCount, error) => {
66+
if (retryCount >= maxRetries) {
67+
throw error
68+
}
69+
return retryCount + 1
70+
}, 0)
71+
)
72+
}
73+
)
74+
);
75+
}
76+
*/
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export const environment = {
2-
backendUrl: 'https://3000-actionanand-angularhttp-six5y8k89a8.ws-us116.gitpod.io/',
3-
// backendUrl: 'http://localhost:3000/',
2+
// backendUrl: 'https://3000-actionanand-angularhttp-six5y8k89a8.ws-us116.gitpod.io/',
3+
backendUrl: 'http://localhost:3000/',
44
};

src/main.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
import { bootstrapApplication } from '@angular/platform-browser';
2+
3+
import { AppComponent } from './app/app.component';
4+
import { appConfig } from './app/app.config';
5+
6+
bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err));
7+
8+
/*
9+
import { bootstrapApplication } from '@angular/platform-browser';
210
import { HttpHandlerFn, HttpRequest, provideHttpClient, withInterceptors } from '@angular/common/http';
311
412
import { AppComponent } from './app/app.component';
513
614
function loggingInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn) {
7-
/*
8-
const newReq = req.clone({
9-
headers: req.headers.set('new-Header', 'headerValue')
10-
});
11-
*/
12-
1315
console.log('Interceptor: ', req);
1416
return next(req);
1517
}
1618
1719
bootstrapApplication(AppComponent, {
1820
providers: [provideHttpClient(withInterceptors([loggingInterceptor]))],
1921
}).catch(err => console.error(err));
22+
*/

0 commit comments

Comments
 (0)