├── src ├── assets │ ├── .gitkeep │ └── digger.gif ├── favicon.ico ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── app │ ├── app.component.css │ ├── components │ │ ├── auth.component.ts │ │ ├── loader.component.css │ │ ├── error.component.ts │ │ ├── header.component.ts │ │ ├── fake.component.ts │ │ ├── https.component.ts │ │ ├── notify.component.ts │ │ ├── cache.component.ts │ │ ├── profiler.component.ts │ │ ├── convert.component.ts │ │ └── loader.component.ts │ ├── services │ │ ├── profiler.service.ts │ │ └── loader.service.ts │ ├── const.ts │ ├── app.component.html │ ├── app.component.ts │ ├── interceptors │ │ ├── header.interceptor.ts │ │ ├── fake.interceptor.ts │ │ ├── https.interceptor.ts │ │ ├── loader.interceptor.ts │ │ ├── notify.interptor.ts │ │ ├── convert.interceptor.ts │ │ ├── error.interceptor.ts │ │ ├── cache.interceptor.ts │ │ ├── index.ts │ │ ├── profiler.interceptor.ts │ │ └── auth.interceptor.ts │ ├── app.module.ts │ └── app-routing.module.ts ├── styles.css ├── tsconfig.app.json ├── main.ts ├── index.html └── polyfills.ts ├── .vscode └── settings.json ├── .angulardoc.json ├── browserslist ├── tsconfig.json ├── .gitignore ├── README.md ├── package.json └── angular.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melcor76/interceptors/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /.angulardoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "repoId": "23e1cbc7-9ad3-486f-be7c-06dd5990431c", 3 | "lastSync": 0 4 | } -------------------------------------------------------------------------------- /src/assets/digger.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melcor76/interceptors/HEAD/src/assets/digger.gif -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | mat-grid-tile { 2 | background: lightblue; 3 | } 4 | 5 | .selected { 6 | background-color: blueviolet; 7 | } 8 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | html, body { height: 100%; } 4 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } 5 | -------------------------------------------------------------------------------- /src/app/components/auth.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | @Component({ 4 | template: ` 5 | 6 | ` 7 | }) 8 | export class AuthComponent {} 9 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/app/services/profiler.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | 3 | @Injectable({ 4 | providedIn: "root" 5 | }) 6 | export class ProfilerService { 7 | add(log: string) { 8 | console.log(log); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/app/const.ts: -------------------------------------------------------------------------------- 1 | export const paths = { 2 | auth: "auth", 3 | cache: "cache", 4 | fake: "fake", 5 | error: "error", 6 | profiler: "profiler", 7 | notify: "notify", 8 | header: "header", 9 | convert: "convert", 10 | loader: "loader", 11 | https: "https" 12 | }; 13 | -------------------------------------------------------------------------------- /src/app/services/loader.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | 3 | @Injectable({ 4 | providedIn: "root" 5 | }) 6 | export class LoaderService { 7 | showLoader = false; 8 | 9 | constructor() {} 10 | 11 | show() { 12 | this.showLoader = true; 13 | } 14 | 15 | hide() { 16 | this.showLoader = false; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/components/loader.component.css: -------------------------------------------------------------------------------- 1 | .loader { 2 | border: 16px solid #f3f3f3; /* Light grey */ 3 | border-top: 16px solid #3498db; /* Blue */ 4 | border-radius: 50%; 5 | width: 100px; 6 | height: 100px; 7 | animation: spin 2s linear infinite; 8 | } 9 | 10 | @keyframes spin { 11 | 0% { 12 | transform: rotate(0deg); 13 | } 14 | 100% { 15 | transform: rotate(360deg); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import "hammerjs"; 2 | import { enableProdMode } from "@angular/core"; 3 | import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; 4 | 5 | import { AppModule } from "./app/app.module"; 6 | import { environment } from "./environments/environment"; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic() 13 | .bootstrapModule(AppModule) 14 | .catch(err => console.error(err)); 15 | -------------------------------------------------------------------------------- /src/app/components/error.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { HttpClient } from "@angular/common/http"; 3 | import { paths } from "../const"; 4 | 5 | @Component({ 6 | template: ` 7 | 8 | ` 9 | }) 10 | export class ErrorComponent { 11 | constructor(private http: HttpClient) {} 12 | 13 | run() { 14 | this.http.get(paths.error).subscribe(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/components/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { HttpClient } from "@angular/common/http"; 3 | import { paths } from "../const"; 4 | 5 | @Component({ 6 | template: ` 7 | 8 | ` 9 | }) 10 | export class HeaderComponent { 11 | constructor(private http: HttpClient) {} 12 | 13 | run() { 14 | this.http.get(paths.header).subscribe(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "typeRoots": ["node_modules/@types"], 15 | "lib": ["es2018", "dom"] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | {{ item.position }}. {{ item.path | titlecase }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/app/components/fake.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { HttpClient } from "@angular/common/http"; 3 | import { paths } from "../const"; 4 | import { Observable } from "rxjs"; 5 | 6 | @Component({ 7 | template: ` 8 |

Response

9 |
{{ response | async | json }}
10 | 13 | ` 14 | }) 15 | export class FakeComponent { 16 | response: Observable; 17 | constructor(private http: HttpClient) {} 18 | 19 | run() { 20 | this.response = this.http.get(paths.fake); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Interceptors 6 | 7 | 8 | 9 | 10 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/app/components/https.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { HttpClient } from "@angular/common/http"; 3 | import { Observable } from "rxjs"; 4 | 5 | @Component({ 6 | template: ` 7 |

Response

8 |
{{ response | async | json }}
9 | 12 | ` 13 | }) 14 | export class HttpsComponent { 15 | response: Observable; 16 | 17 | constructor(private http: HttpClient) {} 18 | 19 | request() { 20 | const url = "http://jsonplaceholder.typicode.com/todos/1"; 21 | this.response = this.http.get(url); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | import { paths } from "./const"; 3 | 4 | interface topList { 5 | path: string; 6 | position: number; 7 | } 8 | 9 | @Component({ 10 | selector: "app-root", 11 | styleUrls: ["app.component.css"], 12 | templateUrl: "app.component.html" 13 | }) 14 | export class AppComponent implements OnInit { 15 | top10: topList[] = []; 16 | selected: number; 17 | 18 | ngOnInit(): void { 19 | const list = Object.keys(paths); 20 | for (let i = list.length; i > 0; i--) { 21 | let newItem: topList = { path: paths[list[i - 1]], position: i }; 22 | this.top10.push(newItem); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/app/interceptors/header.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { 3 | HttpEvent, 4 | HttpRequest, 5 | HttpHandler, 6 | HttpInterceptor 7 | } from "@angular/common/http"; 8 | import { Observable } from "rxjs"; 9 | import { paths } from "../const"; 10 | 11 | @Injectable() 12 | export class HeaderInterceptor implements HttpInterceptor { 13 | intercept( 14 | req: HttpRequest, 15 | next: HttpHandler 16 | ): Observable> { 17 | if (!req.url.includes(paths.header)) { 18 | return next.handle(req); 19 | } 20 | console.warn("HeaderInterceptor"); 21 | 22 | const modified = req.clone({ setHeaders: { "X-Man": "Wolverine" } }); 23 | 24 | return next.handle(modified); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # profiling files 12 | chrome-profiler-events.json 13 | speed-measure-plugin.json 14 | 15 | # IDEs and editors 16 | /.idea 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # IDE - VSCode 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | .history/* 31 | 32 | # misc 33 | /.sass-cache 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | npm-debug.log 38 | yarn-error.log 39 | testem.log 40 | /typings 41 | 42 | # System Files 43 | .DS_Store 44 | Thumbs.db 45 | -------------------------------------------------------------------------------- /src/app/components/notify.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { HttpClient } from "@angular/common/http"; 3 | import { Observable } from "rxjs"; 4 | 5 | @Component({ 6 | template: ` 7 |

Response

8 |
{{ response | async | json }}
9 | 12 | ` 13 | }) 14 | export class NotifyComponent { 15 | response: Observable; 16 | 17 | constructor(private http: HttpClient) {} 18 | 19 | run() { 20 | const body = { 21 | title: "foo", 22 | body: "bar", 23 | userId: 1 24 | }; 25 | const url = "https://jsonplaceholder.typicode.com/posts"; 26 | this.response = this.http.post(url, body); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/interceptors/fake.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { 3 | HttpEvent, 4 | HttpRequest, 5 | HttpHandler, 6 | HttpInterceptor, 7 | HttpResponse 8 | } from "@angular/common/http"; 9 | import { Observable, of } from "rxjs"; 10 | import { paths } from "../const"; 11 | 12 | @Injectable() 13 | export class FakeInterceptor implements HttpInterceptor { 14 | intercept( 15 | req: HttpRequest, 16 | next: HttpHandler 17 | ): Observable> { 18 | if (!req.url.includes(paths.fake)) { 19 | return next.handle(req); 20 | } 21 | console.warn("FakeInterceptor"); 22 | 23 | const body = { firstName: "Mock", lastName: "Faker" }; 24 | return of(new HttpResponse({ status: 200, body: body })); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/components/cache.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { HttpClient } from "@angular/common/http"; 3 | import { paths } from "../const"; 4 | import { Observable } from "rxjs"; 5 | 6 | @Component({ 7 | template: ` 8 |

Response

9 |
{{ response | async | json }}
10 | 11 | 12 | ` 13 | }) 14 | export class CacheComponent { 15 | response: Observable; 16 | constructor(private http: HttpClient) {} 17 | 18 | run() { 19 | const url = "https://jsonplaceholder.typicode.com/todos/2"; 20 | this.response = this.http.get(url); 21 | } 22 | 23 | clear() { 24 | this.response = null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/interceptors/https.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { 3 | HttpEvent, 4 | HttpInterceptor, 5 | HttpHandler, 6 | HttpRequest 7 | } from "@angular/common/http"; 8 | 9 | import { Observable, of } from "rxjs"; 10 | 11 | @Injectable() 12 | export class HttpsInterceptor implements HttpInterceptor { 13 | intercept( 14 | req: HttpRequest, 15 | next: HttpHandler 16 | ): Observable> { 17 | if (!req.url.includes("todos/1")) { 18 | return next.handle(req); 19 | } 20 | console.warn("HttpsInterceptor"); 21 | 22 | // clone request and replace 'http://' with 'https://' at the same time 23 | const httpsReq = req.clone({ 24 | url: req.url.replace("http://", "https://") 25 | }); 26 | 27 | return next.handle(httpsReq); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/components/profiler.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { HttpClient } from "@angular/common/http"; 3 | import { paths } from "../const"; 4 | import { Observable } from "rxjs"; 5 | 6 | @Component({ 7 | template: ` 8 |

Response

9 |
{{ response | async | json }}
10 | 13 | 14 | ` 15 | }) 16 | export class ProfilerComponent { 17 | response: Observable; 18 | 19 | constructor(private http: HttpClient) {} 20 | 21 | succeed() { 22 | const url = "https://jsonplaceholder.typicode.com/users/1"; 23 | this.response = this.http.get(url); 24 | } 25 | 26 | fail() { 27 | this.response = this.http.get(paths.profiler); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/interceptors/loader.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Injector } from "@angular/core"; 2 | import { 3 | HttpEvent, 4 | HttpRequest, 5 | HttpHandler, 6 | HttpInterceptor 7 | } from "@angular/common/http"; 8 | import { Observable } from "rxjs"; 9 | import { finalize, delay } from "rxjs/operators"; 10 | import { LoaderService } from "../services/loader.service"; 11 | 12 | @Injectable() 13 | export class LoaderInterceptor implements HttpInterceptor { 14 | constructor(private injector: Injector) {} 15 | intercept( 16 | req: HttpRequest, 17 | next: HttpHandler 18 | ): Observable> { 19 | if (!req.url.includes("albums")) { 20 | return next.handle(req); 21 | } 22 | console.warn("LoaderInterceptor"); 23 | 24 | const loaderService = this.injector.get(LoaderService); 25 | 26 | loaderService.show(); 27 | 28 | return next.handle(req).pipe( 29 | delay(3000), 30 | finalize(() => loaderService.hide()) 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from "@angular/platform-browser"; 2 | import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; 3 | import { NgModule } from "@angular/core"; 4 | import { HttpClientModule } from "@angular/common/http"; 5 | import { MatCardModule } from "@angular/material/card"; 6 | import { MatGridListModule } from "@angular/material/grid-list"; 7 | import { ToastrModule } from "ngx-toastr"; 8 | 9 | import { AppRoutingModule } from "./app-routing.module"; 10 | import { AppComponent } from "./app.component"; 11 | import { httpInterceptorProviders } from "./interceptors"; 12 | 13 | @NgModule({ 14 | declarations: [AppComponent], 15 | imports: [ 16 | BrowserModule, 17 | BrowserAnimationsModule, 18 | HttpClientModule, 19 | MatCardModule, 20 | MatGridListModule, 21 | ToastrModule.forRoot(), 22 | AppRoutingModule 23 | ], 24 | providers: [httpInterceptorProviders], 25 | bootstrap: [AppComponent] 26 | }) 27 | export class AppModule {} 28 | -------------------------------------------------------------------------------- /src/app/components/convert.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { HttpClient } from "@angular/common/http"; 3 | import { Observable } from "rxjs"; 4 | 5 | @Component({ 6 | template: ` 7 |
8 |
9 |

Request

10 |
{{ requestObj | json }}
11 |
12 |
13 |

Response

14 |
{{ response | async | json }}
15 |
16 |
17 | 20 | ` 21 | }) 22 | export class ConvertComponent { 23 | requestObj = { 24 | Title: "Mr", 25 | Name: "Cool Cat", 26 | Id: 1 27 | }; 28 | response: Observable; 29 | constructor(private http: HttpClient) {} 30 | 31 | run() { 32 | const url = "https://jsonplaceholder.typicode.com/comments/1"; 33 | this.response = this.http.put(url, this.requestObj); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/components/loader.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { HttpClient } from "@angular/common/http"; 3 | import { LoaderService } from "../services/loader.service"; 4 | 5 | @Component({ 6 | styleUrls: ["loader.component.css"], 7 | template: ` 8 |
9 | 17 |
18 |

Response

19 |
{{ response | json }}
20 |
21 |
22 |
23 | ` 24 | }) 25 | export class LoaderComponent { 26 | response; 27 | 28 | constructor(private http: HttpClient, public loaderService: LoaderService) {} 29 | 30 | run() { 31 | const url = "https://jsonplaceholder.typicode.com/albums/1"; 32 | this.http.get(url).subscribe(r => (this.response = r)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/interceptors/notify.interptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { 3 | HttpEvent, 4 | HttpRequest, 5 | HttpHandler, 6 | HttpInterceptor, 7 | HttpErrorResponse, 8 | HttpResponse 9 | } from "@angular/common/http"; 10 | import { Observable } from "rxjs"; 11 | import { tap } from "rxjs/operators"; 12 | import { ToastrService } from "ngx-toastr"; 13 | import { paths } from "../const"; 14 | 15 | @Injectable() 16 | export class NotifyInterceptor implements HttpInterceptor { 17 | constructor(private toastr: ToastrService) {} 18 | intercept( 19 | req: HttpRequest, 20 | next: HttpHandler 21 | ): Observable> { 22 | if (!req.url.includes("posts")) { 23 | return next.handle(req); 24 | } 25 | console.warn("NotifyInterceptor"); 26 | 27 | return next.handle(req).pipe( 28 | tap((event: HttpEvent) => { 29 | if (event instanceof HttpResponse && event.status === 201) { 30 | this.toastr.success("Object created."); 31 | } 32 | }) 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/interceptors/convert.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { 3 | HttpEvent, 4 | HttpRequest, 5 | HttpHandler, 6 | HttpInterceptor, 7 | HttpResponse 8 | } from "@angular/common/http"; 9 | import { Observable, of } from "rxjs"; 10 | import { camelCase, mapKeys } from "lodash"; 11 | import { paths } from "../const"; 12 | import { map } from "rxjs/operators"; 13 | 14 | @Injectable() 15 | export class ConvertInterceptor implements HttpInterceptor { 16 | intercept( 17 | req: HttpRequest, 18 | next: HttpHandler 19 | ): Observable> { 20 | if (!req.url.includes("comments")) { 21 | return next.handle(req); 22 | } 23 | console.warn("ConvertInterceptor"); 24 | 25 | return next.handle(req).pipe( 26 | map((event: HttpEvent) => { 27 | if (event instanceof HttpResponse) { 28 | let camelCaseObject = mapKeys(event.body, (v, k) => camelCase(k)); 29 | const modEvent = event.clone({ body: camelCaseObject }); 30 | 31 | return modEvent; 32 | } 33 | }) 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Interceptors 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.0. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /src/app/interceptors/error.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { 3 | HttpEvent, 4 | HttpRequest, 5 | HttpHandler, 6 | HttpInterceptor, 7 | HttpErrorResponse 8 | } from "@angular/common/http"; 9 | import { Observable, throwError } from "rxjs"; 10 | import { catchError, retry } from "rxjs/operators"; 11 | import { ToastrService } from "ngx-toastr"; 12 | import { paths } from "../const"; 13 | 14 | @Injectable() 15 | export class ErrorInterceptor implements HttpInterceptor { 16 | constructor(private toastr: ToastrService) {} 17 | intercept( 18 | req: HttpRequest, 19 | next: HttpHandler 20 | ): Observable> { 21 | if (!req.url.includes(paths.error)) { 22 | return next.handle(req); 23 | } 24 | console.warn("ErrorInterceptor"); 25 | 26 | return next.handle(req).pipe( 27 | retry(2), 28 | catchError((error: HttpErrorResponse) => { 29 | if (error.status !== 401) { 30 | // 401 handled in auth.interceptor 31 | this.toastr.error(error.message); 32 | } 33 | return throwError(error); 34 | }) 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/interceptors/cache.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { 3 | HttpEvent, 4 | HttpRequest, 5 | HttpHandler, 6 | HttpInterceptor, 7 | HttpResponse 8 | } from "@angular/common/http"; 9 | import { Observable, of } from "rxjs"; 10 | import { tap } from "rxjs/operators"; 11 | import { paths } from "../const"; 12 | 13 | @Injectable() 14 | export class CacheInterceptor implements HttpInterceptor { 15 | private cache = new Map(); 16 | 17 | intercept( 18 | req: HttpRequest, 19 | next: HttpHandler 20 | ): Observable> { 21 | if (!req.url.includes("todos/2")) { 22 | return next.handle(req); 23 | } 24 | console.warn("CacheInterceptor"); 25 | 26 | if (req.method !== "GET") { 27 | return next.handle(req); 28 | } 29 | 30 | const cachedResponse = this.cache.get(req.url); 31 | if (cachedResponse) { 32 | return of(cachedResponse); 33 | } 34 | 35 | return next.handle(req).pipe( 36 | tap(event => { 37 | if (event instanceof HttpResponse) { 38 | this.cache.set(req.url, event); 39 | } 40 | }) 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "interceptors", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~8.0.0", 15 | "@angular/cdk": "~8.0.1", 16 | "@angular/common": "~8.0.0", 17 | "@angular/compiler": "~8.0.0", 18 | "@angular/core": "~8.0.0", 19 | "@angular/forms": "~8.0.0", 20 | "@angular/material": "~8.0.1", 21 | "@angular/platform-browser": "~8.0.0", 22 | "@angular/platform-browser-dynamic": "~8.0.0", 23 | "@angular/router": "~8.0.0", 24 | "core-js": "2.6.9", 25 | "hammerjs": "^2.0.8", 26 | "lodash": "4.17.11", 27 | "ngx-toastr": "10.0.4", 28 | "rxjs": "~6.5.2", 29 | "tslib": "1.10.0", 30 | "zone.js": "~0.9.1" 31 | }, 32 | "devDependencies": { 33 | "@angular-devkit/build-angular": "~0.800.0", 34 | "@angular/cli": "~8.0.2", 35 | "@angular/compiler-cli": "~8.0.0", 36 | "@angular/language-service": "~8.0.0", 37 | "@types/node": "~8.9.4", 38 | "codelyzer": "^5.0.1", 39 | "ts-node": "~7.0.0", 40 | "tslint": "~5.11.0", 41 | "typescript": "3.4.5" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app/interceptors/index.ts: -------------------------------------------------------------------------------- 1 | import { HTTP_INTERCEPTORS } from '@angular/common/http'; 2 | 3 | import { NotifyInterceptor } from './notify.interptor'; 4 | import { HttpsInterceptor } from './https.interceptor'; 5 | import { ProfilerInterceptor } from './profiler.interceptor'; 6 | import { AuthInterceptor } from './auth.interceptor'; 7 | import { CacheInterceptor } from './cache.interceptor'; 8 | import { HeaderInterceptor } from './header.interceptor'; 9 | import { ErrorInterceptor } from './error.interceptor'; 10 | import { FakeInterceptor } from './fake.interceptor'; 11 | import { LoaderInterceptor } from './loader.interceptor'; 12 | import { ConvertInterceptor } from './convert.interceptor'; 13 | 14 | export const httpInterceptorProviders = [ 15 | { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }, 16 | { provide: HTTP_INTERCEPTORS, useClass: CacheInterceptor, multi: true }, 17 | { provide: HTTP_INTERCEPTORS, useClass: ConvertInterceptor, multi: true }, 18 | { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }, 19 | { provide: HTTP_INTERCEPTORS, useClass: FakeInterceptor, multi: true }, 20 | { provide: HTTP_INTERCEPTORS, useClass: HeaderInterceptor, multi: true }, 21 | { provide: HTTP_INTERCEPTORS, useClass: HttpsInterceptor, multi: true }, 22 | { provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptor, multi: true }, 23 | { provide: HTTP_INTERCEPTORS, useClass: ProfilerInterceptor, multi: true }, 24 | { provide: HTTP_INTERCEPTORS, useClass: NotifyInterceptor, multi: true } 25 | ]; 26 | -------------------------------------------------------------------------------- /src/app/interceptors/profiler.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { 3 | HttpEvent, 4 | HttpRequest, 5 | HttpHandler, 6 | HttpInterceptor, 7 | HttpResponse 8 | } from "@angular/common/http"; 9 | import { Observable } from "rxjs"; 10 | import { tap, finalize } from "rxjs/operators"; 11 | import { ProfilerService } from "../services/profiler.service"; 12 | import { paths } from "../const"; 13 | 14 | @Injectable() 15 | export class ProfilerInterceptor implements HttpInterceptor { 16 | constructor(private profiler: ProfilerService) {} 17 | 18 | intercept( 19 | req: HttpRequest, 20 | next: HttpHandler 21 | ): Observable> { 22 | if (!req.url.includes(paths.profiler) && !req.url.includes("users")) { 23 | return next.handle(req); 24 | } 25 | console.warn("ProfilerInterceptor"); 26 | 27 | const started = Date.now(); 28 | let ok: string; 29 | 30 | return next.handle(req).pipe( 31 | tap( 32 | // Succeeds when there is a response; ignore other events 33 | (event: HttpEvent) => { 34 | if (event instanceof HttpResponse) { 35 | ok = "succeeded"; 36 | } 37 | }, 38 | // Operation failed; error is an HttpErrorResponse 39 | error => (ok = "failed") 40 | ), 41 | // Log when response observable either completes or errors 42 | finalize(() => { 43 | const elapsed = Date.now() - started; 44 | const msg = `${req.method} "${req.urlWithParams}" 45 | ${ok} in ${elapsed} ms.`; 46 | this.profiler.add(msg); 47 | }) 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { Routes, RouterModule } from "@angular/router"; 3 | import { AuthComponent } from "./components/auth.component"; 4 | import { CacheComponent } from "./components/cache.component"; 5 | import { ConvertComponent } from "./components/convert.component"; 6 | import { NotifyComponent } from "./components/notify.component"; 7 | import { ErrorComponent } from "./components/error.component"; 8 | import { FakeComponent } from "./components/fake.component"; 9 | import { HeaderComponent } from "./components/header.component"; 10 | import { LoaderComponent } from "./components/loader.component"; 11 | import { ProfilerComponent } from "./components/profiler.component"; 12 | import { CommonModule } from "@angular/common"; 13 | import { HttpsComponent } from "./components/https.component"; 14 | import { paths } from "./const"; 15 | import { MatButtonModule } from "@angular/material/button"; 16 | 17 | const routes: Routes = [ 18 | { path: paths.auth, component: AuthComponent }, 19 | { path: paths.cache, component: CacheComponent }, 20 | { path: paths.convert, component: ConvertComponent }, 21 | { path: paths.error, component: ErrorComponent }, 22 | { path: paths.fake, component: FakeComponent }, 23 | { path: paths.header, component: HeaderComponent }, 24 | { path: paths.https, component: HttpsComponent }, 25 | { path: paths.loader, component: LoaderComponent }, 26 | { path: paths.profiler, component: ProfilerComponent }, 27 | { path: paths.notify, component: NotifyComponent } 28 | ]; 29 | 30 | @NgModule({ 31 | declarations: [ 32 | AuthComponent, 33 | CacheComponent, 34 | ConvertComponent, 35 | ErrorComponent, 36 | FakeComponent, 37 | HeaderComponent, 38 | HttpsComponent, 39 | LoaderComponent, 40 | NotifyComponent, 41 | ProfilerComponent 42 | ], 43 | imports: [CommonModule, MatButtonModule, RouterModule.forRoot(routes)], 44 | exports: [ 45 | AuthComponent, 46 | CacheComponent, 47 | ConvertComponent, 48 | ErrorComponent, 49 | FakeComponent, 50 | HeaderComponent, 51 | HttpsComponent, 52 | LoaderComponent, 53 | NotifyComponent, 54 | ProfilerComponent, 55 | RouterModule 56 | ] 57 | }) 58 | export class AppRoutingModule {} 59 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 25 | import "core-js/es7/reflect"; 26 | 27 | /** 28 | * Web Animations `@angular/platform-browser/animations` 29 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 30 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 31 | */ 32 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 33 | 34 | /** 35 | * By default, zone.js will patch all possible macroTask and DomEvents 36 | * user can disable parts of macroTask/DomEvents patch by setting following flags 37 | * because those flags need to be set before `zone.js` being loaded, and webpack 38 | * will put import in the top of bundle, so user need to create a separate file 39 | * in this directory (for example: zone-flags.ts), and put the following flags 40 | * into that file, and then add the following code before importing zone.js. 41 | * import './zone-flags.ts'; 42 | * 43 | * The flags allowed in zone-flags.ts are listed here. 44 | * 45 | * The following flags will work for all browsers. 46 | * 47 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 48 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 49 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 50 | * 51 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 52 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 53 | * 54 | * (window as any).__Zone_enable_cross_context_check = true; 55 | * 56 | */ 57 | 58 | /*************************************************************************************************** 59 | * Zone JS is required by default for Angular itself. 60 | */ 61 | import "zone.js/dist/zone"; // Included with Angular CLI. 62 | 63 | /*************************************************************************************************** 64 | * APPLICATION IMPORTS 65 | */ 66 | -------------------------------------------------------------------------------- /src/app/interceptors/auth.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { 3 | HttpEvent, 4 | HttpInterceptor, 5 | HttpHandler, 6 | HttpRequest, 7 | HttpErrorResponse 8 | } from "@angular/common/http"; 9 | import { throwError, Observable, BehaviorSubject, of } from "rxjs"; 10 | import { catchError, filter, finalize, take, switchMap } from "rxjs/operators"; 11 | import { paths } from "../const"; 12 | 13 | @Injectable() 14 | export class AuthInterceptor implements HttpInterceptor { 15 | private AUTH_HEADER = "Authorization"; 16 | private token = "secrettoken"; 17 | private refreshTokenInProgress = false; 18 | private refreshTokenSubject: BehaviorSubject = new BehaviorSubject( 19 | null 20 | ); 21 | 22 | intercept( 23 | req: HttpRequest, 24 | next: HttpHandler 25 | ): Observable> { 26 | if (!req.url.includes(paths.auth)) { 27 | return next.handle(req); 28 | } 29 | console.warn("AuthInterceptor"); 30 | if (!req.headers.has("Content-Type")) { 31 | req = req.clone({ 32 | headers: req.headers.set("Content-Type", "application/json") 33 | }); 34 | } 35 | 36 | req = this.addAuthenticationToken(req); 37 | 38 | return next.handle(req).pipe( 39 | catchError((error: HttpErrorResponse) => { 40 | if (error && error.status === 401) { 41 | // 401 errors are most likely going to be because we have an expired token that we need to refresh. 42 | if (this.refreshTokenInProgress) { 43 | // If refreshTokenInProgress is true, we will wait until refreshTokenSubject has a non-null value 44 | // which means the new token is ready and we can retry the request again 45 | return this.refreshTokenSubject.pipe( 46 | filter(result => result !== null), 47 | take(1), 48 | switchMap(() => next.handle(this.addAuthenticationToken(req))) 49 | ); 50 | } else { 51 | this.refreshTokenInProgress = true; 52 | 53 | // Set the refreshTokenSubject to null so that subsequent API calls will wait until the new token has been retrieved 54 | this.refreshTokenSubject.next(null); 55 | 56 | return this.refreshAccessToken().pipe( 57 | switchMap((success: boolean) => { 58 | this.refreshTokenSubject.next(success); 59 | return next.handle(this.addAuthenticationToken(req)); 60 | }), 61 | // When the call to refreshToken completes we reset the refreshTokenInProgress to false 62 | // for the next time the token needs to be refreshed 63 | finalize(() => (this.refreshTokenInProgress = false)) 64 | ); 65 | } 66 | } else { 67 | return throwError(error); 68 | } 69 | }) 70 | ); 71 | } 72 | 73 | private refreshAccessToken(): Observable { 74 | return of("secret token"); 75 | } 76 | 77 | private addAuthenticationToken(request: HttpRequest): HttpRequest { 78 | // If we do not have a token yet then we should not set the header. 79 | // Here we could first retrieve the token from where we store it. 80 | if (!this.token) { 81 | return request; 82 | } 83 | // If you are calling an outside domain then do not add the token. 84 | if (!request.url.match(/www.mydomain.com\//)) { 85 | return request; 86 | } 87 | return request.clone({ 88 | headers: request.headers.set(this.AUTH_HEADER, "Bearer " + this.token) 89 | }); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "interceptors": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": { 12 | "@schematics/angular:class": { 13 | "skipTests": true 14 | }, 15 | "@schematics/angular:component": { 16 | "skipTests": true 17 | }, 18 | "@schematics/angular:directive": { 19 | "skipTests": true 20 | }, 21 | "@schematics/angular:guard": { 22 | "skipTests": true 23 | }, 24 | "@schematics/angular:module": { 25 | "skipTests": true 26 | }, 27 | "@schematics/angular:pipe": { 28 | "skipTests": true 29 | }, 30 | "@schematics/angular:service": { 31 | "skipTests": true 32 | } 33 | }, 34 | "architect": { 35 | "build": { 36 | "builder": "@angular-devkit/build-angular:browser", 37 | "options": { 38 | "outputPath": "dist/interceptors", 39 | "index": "src/index.html", 40 | "main": "src/main.ts", 41 | "polyfills": "src/polyfills.ts", 42 | "tsConfig": "src/tsconfig.app.json", 43 | "assets": ["src/favicon.ico", "src/assets"], 44 | "styles": [ 45 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 46 | "src/styles.css", 47 | "node_modules/ngx-toastr/toastr.css" 48 | ], 49 | "scripts": [], 50 | "es5BrowserSupport": true 51 | }, 52 | "configurations": { 53 | "production": { 54 | "fileReplacements": [ 55 | { 56 | "replace": "src/environments/environment.ts", 57 | "with": "src/environments/environment.prod.ts" 58 | } 59 | ], 60 | "optimization": true, 61 | "outputHashing": "all", 62 | "sourceMap": false, 63 | "extractCss": true, 64 | "namedChunks": false, 65 | "aot": true, 66 | "extractLicenses": true, 67 | "vendorChunk": false, 68 | "buildOptimizer": true, 69 | "budgets": [ 70 | { 71 | "type": "initial", 72 | "maximumWarning": "2mb", 73 | "maximumError": "5mb" 74 | } 75 | ] 76 | } 77 | } 78 | }, 79 | "serve": { 80 | "builder": "@angular-devkit/build-angular:dev-server", 81 | "options": { 82 | "browserTarget": "interceptors:build" 83 | }, 84 | "configurations": { 85 | "production": { 86 | "browserTarget": "interceptors:build:production" 87 | } 88 | } 89 | }, 90 | "extract-i18n": { 91 | "builder": "@angular-devkit/build-angular:extract-i18n", 92 | "options": { 93 | "browserTarget": "interceptors:build" 94 | } 95 | }, 96 | "lint": { 97 | "builder": "@angular-devkit/build-angular:tslint", 98 | "options": { 99 | "tsConfig": ["src/tsconfig.app.json", "src/tsconfig.spec.json"], 100 | "exclude": ["**/node_modules/**"] 101 | } 102 | } 103 | } 104 | } 105 | }, 106 | "defaultProject": "interceptors" 107 | } 108 | --------------------------------------------------------------------------------