├── 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 |
--------------------------------------------------------------------------------