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:
parent
19114dc11e
commit
529f0a83cb
|
@ -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 () => {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -14,6 +14,7 @@ import { catchError, retry } from 'rxjs/operators';
|
|||
export interface Config {
|
||||
heroesUrl: string;
|
||||
textfile: string;
|
||||
date: any;
|
||||
}
|
||||
// #enddocregion config-interface
|
||||
// #docregion proto
|
||||
|
|
|
@ -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
|
|
@ -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 },
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"heroesUrl": "api/heroes",
|
||||
"textfile": "assets/textfile.txt"
|
||||
"textfile": "assets/textfile.txt",
|
||||
"date": "2020-01-29"
|
||||
}
|
||||
|
|
|
@ -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()`.
|
||||
|
||||
|
|
Loading…
Reference in New Issue