docs(http): add custom JSONParser example (#40645)

Update the HTTP guide and associated example to demonstrate
how an interceptor can be used to provide a custom JSON parser.

Resolves #21079

PR Close #40645
This commit is contained in:
Pete Bacon Darwin 2021-01-30 22:17:27 +00:00 committed by Joey Perrott
parent 19114dc11e
commit 529f0a83cb
8 changed files with 118 additions and 6 deletions

View File

@ -66,6 +66,7 @@ describe('Http Tests', () => {
await checkLogForMessage('GET "assets/config.json"');
expect(await page.configSpan.getText()).toContain('Heroes API URL is "api/heroes"');
expect(await page.configSpan.getText()).toContain('Textfile URL is "assets/textfile.txt"');
expect(await page.configSpan.getText()).toContain('Date is "Wed Jan 29 2020" (date)');
});
it('can fetch the configuration JSON file with headers', async () => {

View File

@ -7,6 +7,7 @@
<span *ngIf="config">
<p>Heroes API URL is "{{config.heroesUrl}}"</p>
<p>Textfile URL is "{{config.textfile}}"</p>
<p>Date is "{{config.date.toDateString()}}" ({{getType(config.date)}})</p>
<div *ngIf="headers">
Response headers:
<ul>

View File

@ -2,7 +2,6 @@
// #docregion
import { Component } from '@angular/core';
import { Config, ConfigService } from './config.service';
import { MessageService } from '../message.service';
@Component({
selector: 'app-config',
@ -40,7 +39,8 @@ export class ConfigComponent {
// #docregion v1
.subscribe((data: Config) => this.config = {
heroesUrl: data.heroesUrl,
textfile: data.textfile
textfile: data.textfile,
date: data.date,
});
}
// #enddocregion v1
@ -71,5 +71,9 @@ export class ConfigComponent {
makeError() {
this.configService.makeIntentionalError().subscribe(null, error => this.error = error );
}
getType(val: any): string {
return val instanceof Date ? 'date' : Array.isArray(val) ? 'array' : typeof val;
}
}
// #enddocregion

View File

@ -14,6 +14,7 @@ import { catchError, retry } from 'rxjs/operators';
export interface Config {
heroesUrl: string;
textfile: string;
date: any;
}
// #enddocregion config-interface
// #docregion proto

View File

@ -0,0 +1,62 @@
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { map } from 'rxjs/operators';
// #docregion custom-json-interceptor
@Injectable()
export class CustomJsonInterceptor implements HttpInterceptor {
constructor(private jsonParser: JsonParser) {}
intercept(httpRequest: HttpRequest<any>, next: HttpHandler) {
if (httpRequest.responseType === 'json') {
// If the expected response type is JSON then handle it here.
return this.handleJsonResponse(httpRequest, next);
} else {
return next.handle(httpRequest);
}
}
private handleJsonResponse(httpRequest: HttpRequest<any>, next: HttpHandler) {
// Override the responseType to disable the default JSON parsing.
httpRequest = httpRequest.clone({responseType: 'text'});
// Handle the response using the custom parser.
return next.handle(httpRequest).pipe(map(event => this.parseJsonResponse(event)));
}
private parseJsonResponse(event: HttpEvent<any>) {
if (event instanceof HttpResponse && typeof event.body === 'string') {
return event.clone({body: this.jsonParser.parse(event.body)});
} else {
return event;
}
}
}
// The JsonParser class acts as a base class for custom parsers and as the DI token.
@Injectable()
export abstract class JsonParser {
abstract parse(text: string): any;
}
// #enddocregion custom-json-interceptor
// #docregion custom-json-parser
@Injectable()
export class CustomJsonParser implements JsonParser {
parse(text: string): any {
return JSON.parse(text, dateReviver);
}
}
function dateReviver(key: string, value: any) {
// #enddocregion custom-json-parser
if (typeof value !== 'string') {
return value;
}
const match = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(value);
if (!match) {
return value;
}
return new Date(+match[1], +match[2] - 1, +match[3]);
// #docregion custom-json-parser
}
// #enddocregion custom-json-parser

View File

@ -6,6 +6,7 @@ import { HTTP_INTERCEPTORS } from '@angular/common/http';
// #enddocregion interceptor-providers
import { AuthInterceptor } from './auth-interceptor';
import { CachingInterceptor } from './caching-interceptor';
import { CustomJsonInterceptor , CustomJsonParser, JsonParser} from './custom-json-interceptor';
import { EnsureHttpsInterceptor } from './ensure-https-interceptor';
import { LoggingInterceptor } from './logging-interceptor';
// #docregion interceptor-providers
@ -21,6 +22,10 @@ export const httpInterceptorProviders = [
// #docregion noop-provider
{ provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },
// #enddocregion noop-provider, interceptor-providers
// #docregion custom-json-interceptor
{ provide: HTTP_INTERCEPTORS, useClass: CustomJsonInterceptor, multi: true },
{ provide: JsonParser, useClass: CustomJsonParser },
// #enddocregion custom-json-interceptor
{ provide: HTTP_INTERCEPTORS, useClass: EnsureHttpsInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: TrimNameInterceptor, multi: true },

View File

@ -1,4 +1,5 @@
{
"heroesUrl": "api/heroes",
"textfile": "assets/textfile.txt"
"textfile": "assets/textfile.txt",
"date": "2020-01-29"
}

View File

@ -741,6 +741,10 @@ To do this, set the cloned request body to `null`.
newReq = req.clone({ body: null }); // clear the body
```
## Http interceptor use-cases
Below are a number of common uses for interceptors.
### Setting default headers
Apps often use an interceptor to set default headers on outgoing requests.
@ -768,7 +772,7 @@ An interceptor that alters headers can be used for a number of different operati
* Caching behavior; for example, `If-Modified-Since`
* XSRF protection
### Using interceptors for logging
### Logging request and response pairs
Because interceptors can process the request and response _together_, they can perform tasks such as timing and logging an entire HTTP operation.
@ -788,9 +792,42 @@ and reports the outcome to the `MessageService`.
Neither `tap` nor `finalize` touch the values of the observable stream returned to the caller.
{@a caching}
{@a custom-json-parser}
### Using interceptors for caching
### Custom JSON parsing
Interceptors can be used to replace the built-in JSON parsing with a custom implementation.
The `CustomJsonInterceptor` in the following example demonstrates how to achieve this.
If the intercepted request expects a `'json'` response, the `reponseType` is changed to `'text'`
to disable the built-in JSON parsing. Then the response is parsed via the injected `JsonParser`.
<code-example
path="http/src/app/http-interceptors/custom-json-interceptor.ts"
region="custom-json-interceptor"
header="app/http-interceptors/custom-json-interceptor.ts">
</code-example>
You can then implement your own custom `JsonParser`.
Here is a custom JsonParser that has a special date reviver.
<code-example
path="http/src/app/http-interceptors/custom-json-interceptor.ts"
region="custom-json-parser"
header="app/http-interceptors/custom-json-interceptor.ts">
</code-example>
You provide the `CustomParser` along with the `CustomJsonInterceptor`.
<code-example
path="http/src/app/http-interceptors/index.ts"
region="custom-json-interceptor"
header="app/http-interceptors/index.ts">
</code-example>
{@a caching}
### Caching requests
Interceptors can handle requests by themselves, without forwarding to `next.handle()`.