docs: Migrate HttpClient guide and its code examples (/http) to standalone (#51400)

**Unit Testing Code Does Not Compile**

The compilation error is:
```
The compilation error is
./src/testing/http-client.spec.ts - Error: Module build failed (from ./node_modules/@ngtools/webpack/src/ivy/index.js):
Error: /home/projects/obk3vc--run/src/testing/http-client.spec.ts is missing from the TypeScript compilation. Please make sure it is in your tsconfig via the 'files' or 'include' property.
```
I’m not sure what to say about unit testing HTTP in a full Standalone app. Is it different?

_This is the only known remaining defect in this conversion of HTTP to Standalone._

**Edited content of  `http-request-data-from-server.md`**

The current version of this page is confusing. In particular

* It tells readers they **should always unsubscribe** from the HttpClient method calls. This is *not true* and this example doesn't even do it. I replaced this instruction with more nuanced advice and an explanation of why it is OK to not unsubscribe to HttpClient methods.

* There is a "helpful" note about using the RxJS `map` operator to transform the response. This is *not "helpfulf"*. It is *confusing* because the sample doesn't use `map` anywhere. It was unnecessary here, even if it might be helpful elsewhere. I removed this note.

* The "Requesting a typed response" section seemed unclear to me, particularly because the guide begins with a `get` request that already has the `Config` return type specification. My revision attempts to make this more clear.

* The bold "callout" about the `observe` and `responseType` options appears out of nowhere after "Requesting a typed response". It's disconcerting at best. I moved it to the bottom of the page and linked to it from the `options` discussion at the top.

I made a few other revisions that I hope improve the readability of this page.

**Corrected `http-make-jsonp-request.md`**

The JSONP example, handwritten in the guide page, would not have compiled. I added one that does to `heroes.service.ts` and displayed it on this page.

**Corrected `http-handle-request-errors.md`

This page ended with a section called "Sending data to a server" that introduces PUT, POST, and DELETE. These features have nothing to do with error handling and the verbiage here duplicates the opening paragraphs of the next topic which does: "Send data to a server". So I deleted this section from the error handling guide page.

**Archived http-setup-server-communication.md**

`http-setup-server-communication.md` appears to be the original long document that has since been divided over the other pages in this folder.

It shouldn’t be in the reader’s flow. I did update it for Standalone. But I also removed it from left-nav and marked as archived.

PR Close #51400
This commit is contained in:
Ward Bell 2023-08-16 22:11:14 -07:00 committed by Jessica Janiuk
parent 49415e8221
commit 8ef5cc680d
27 changed files with 352 additions and 288 deletions

View file

@ -1,8 +1,27 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ConfigComponent } from './config/config.component';
import { DownloaderComponent } from './downloader/downloader.component';
import { HeroesComponent } from './heroes/heroes.component';
import { MessagesComponent } from './messages/messages.component';
import { PackageSearchComponent } from './package-search/package-search.component';
import { UploaderComponent } from './uploader/uploader.component';
@Component({
standalone: true,
selector: 'app-root',
templateUrl: './app.component.html',
imports: [
CommonModule,
ConfigComponent,
DownloaderComponent,
HeroesComponent,
MessagesComponent,
PackageSearchComponent,
UploaderComponent
],
styleUrls: ['./app.component.css']
})
export class AppComponent {

View file

@ -1,89 +0,0 @@
// #docplaster
// #docregion sketch
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
// #enddocregion sketch
import { FormsModule } from '@angular/forms';
// #docregion sketch
import { HttpClientModule } from '@angular/common/http';
// #enddocregion sketch
import { HttpClientXsrfModule } from '@angular/common/http';
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './in-memory-data.service';
import { RequestCache, RequestCacheWithMap } from './request-cache.service';
import { AppComponent } from './app.component';
import { AuthService } from './auth.service';
import { ConfigComponent } from './config/config.component';
import { DownloaderComponent } from './downloader/downloader.component';
import { HeroesComponent } from './heroes/heroes.component';
import { HttpErrorHandler } from './http-error-handler.service';
import { MessageService } from './message.service';
import { MessagesComponent } from './messages/messages.component';
import { PackageSearchComponent } from './package-search/package-search.component';
import { UploaderComponent } from './uploader/uploader.component';
import { httpInterceptorProviders } from './http-interceptors/index';
// #docregion sketch
@NgModule({
// #docregion xsrf
imports: [
// #enddocregion xsrf
BrowserModule,
// #enddocregion sketch
FormsModule,
// #docregion sketch
// import HttpClientModule after BrowserModule.
// #docregion xsrf
HttpClientModule,
// #enddocregion sketch
HttpClientXsrfModule.withOptions({
cookieName: 'My-Xsrf-Cookie',
headerName: 'My-Xsrf-Header',
}),
// #enddocregion xsrf
// The HttpClientInMemoryWebApiModule module intercepts HTTP requests
// and returns simulated server responses.
// Remove it when a real server is ready to receive requests.
HttpClientInMemoryWebApiModule.forRoot(
InMemoryDataService, {
dataEncapsulation: false,
passThruUnknownUrl: true,
put204: false // return entity after PUT/update
}
)
// #docregion sketch, xsrf
],
// #enddocregion xsrf
declarations: [
AppComponent,
// #enddocregion sketch
ConfigComponent,
DownloaderComponent,
HeroesComponent,
MessagesComponent,
UploaderComponent,
PackageSearchComponent,
// #docregion sketch
],
// #enddocregion sketch
// #docregion interceptor-providers
providers: [
// #enddocregion interceptor-providers
AuthService,
HttpErrorHandler,
MessageService,
{ provide: RequestCache, useClass: RequestCacheWithMap },
// #docregion interceptor-providers
httpInterceptorProviders
],
// #enddocregion interceptor-providers
// #docregion sketch
bootstrap: [ AppComponent ]
})
export class AppModule {}
// #enddocregion sketch

View file

@ -1,55 +1,72 @@
// #docplaster
// #docregion
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Config, ConfigService } from './config.service';
@Component({
standalone: true,
selector: 'app-config',
templateUrl: './config.component.html',
imports: [ CommonModule ],
providers: [ ConfigService ],
styles: ['.error { color: #b30000; }']
})
export class ConfigComponent {
error: any;
headers: string[] = [];
// #docregion v2
// #docregion typed_response, v2
config: Config | undefined;
// #enddocregion v2
// #enddocregion typed_response, v2
// #docregion v1
constructor(private configService: ConfigService) {}
// #enddocregion v1
clear() {
this.config = undefined;
this.error = undefined;
this.headers = [];
}
// #docregion v1, v2
// #docregion v1, v2, typed_response, untyped_response
showConfig() {
this.configService.getConfig()
// #enddocregion v1, v2
// #enddocregion v1, v2, typed_response, untyped_response
.subscribe({
next: (data: Config) => this.config = { ...data }, // success path
next: data => this.config = { ...data }, // success path
error: error => this.error = error, // error path
});
}
showConfig_v1() {
this.configService.getConfig_1()
// #docregion v1
.subscribe((data: Config) => this.config = {
// #docregion typed_response, v1
.subscribe(data => this.config = {
heroesUrl: data.heroesUrl,
textfile: data.textfile,
date: data.date,
});
}
// #enddocregion v1
// #enddocregion typed_response, v1
showConfig_untyped_response() {
this.configService.getConfig_untyped_response()
// #docregion untyped_response
.subscribe(data => this.config = {
heroesUrl: (data as any).heroesUrl,
textfile: (data as any).textfile,
date: (data as any).date,
});
}
// #enddocregion untyped_response
showConfig_v2() {
this.configService.getConfig()
// #docregion v2
// clone the data object, using its known Config shape
.subscribe((data: Config) => this.config = { ...data });
.subscribe(data => this.config = { ...data });
}
// #enddocregion v2
@ -76,4 +93,4 @@ export class ConfigComponent {
return val instanceof Date ? 'date' : Array.isArray(val) ? 'array' : typeof val;
}
}
// #enddocregion

View file

@ -5,10 +5,7 @@ import { HttpClient } from '@angular/common/http';
// #enddocregion proto
import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
// #docregion rxjs-imports
import { Observable, throwError } from 'rxjs';
import { catchError, retry } from 'rxjs/operators';
// #enddocregion rxjs-imports
import { Observable, catchError, retry, throwError } from 'rxjs';
// #docregion config-interface
export interface Config {
@ -63,6 +60,13 @@ export class ConfigService {
}
// #enddocregion getConfig_3
getConfig_untyped_response() {
// #docregion untyped_response
return this.http.get(this.configUrl);
// #enddocregion untyped_response
}
// #docregion getConfigResponse
getConfigResponse(): Observable<HttpResponse<Config>> {
return this.http.get<Config>(

View file

@ -1,9 +1,12 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DownloaderService } from './downloader.service';
@Component({
standalone: true,
selector: 'app-downloader',
templateUrl: './downloader.component.html',
imports: [ CommonModule ],
providers: [ DownloaderService ]
})
export class DownloaderComponent {

View file

@ -1,11 +1,14 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Hero } from './hero';
import { HeroesService } from './heroes.service';
@Component({
standalone: true,
selector: 'app-heroes',
templateUrl: './heroes.component.html',
imports: [ CommonModule, FormsModule ],
providers: [HeroesService],
styleUrls: ['./heroes.component.css']
})

View file

@ -6,8 +6,7 @@ import { HttpHeaders } from '@angular/common/http';
// #enddocregion http-options
import { Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Observable, catchError, map } from 'rxjs';
import { Hero } from './hero';
import { HttpErrorHandler, HandleError } from '../http-error-handler.service';
@ -40,9 +39,10 @@ export class HeroesService {
);
}
// #docregion searchHeroes
// #docregion searchHeroes, searchHeroesJsonp
/* GET heroes whose name contains search term */
searchHeroes(term: string): Observable<Hero[]> {
// #enddocregion searchHeroesJsonp
term = term.trim();
// Add safe, URL encoded search parameter if there is a search term
@ -56,6 +56,27 @@ export class HeroesService {
}
// #enddocregion searchHeroes
// This JSONP example doesn't run. It is for the JSONP documentation only.
/** Imaginary API in a different domain that supports JSONP. */
heroesSearchUrl = 'https://heroes.com/search';
/** Does whatever is necessary to convert the result from API to Heroes */
jsonpResultToHeroes(result: any) { return result as Hero[]; }
/* GET heroes (using JSONP) whose name contains search term */
searchHeroesJsonp(term: string): Observable<Hero[]> {
// #docregion searchHeroesJsonp
term = term.trim();
const heroesUrl = `${this.heroesSearchUrl}?${term}`;
return this.http.jsonp(heroesUrl, 'callback')
.pipe(
map(result => this.jsonpResultToHeroes(result)),
catchError(this.handleError('searchHeroes', []))
);
}
// #enddocregion searchHeroesJsonp
//////// Save methods //////////
// #docregion addHero

View file

@ -1,7 +1,7 @@
// #docplaster
import { Injectable } from '@angular/core';
import {
HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
HttpInterceptor, HttpHandler, HttpRequest
} from '@angular/common/http';
// #docregion

View file

@ -1,12 +1,10 @@
// #docplaster
import { Injectable } from '@angular/core';
import {
HttpEvent, HttpHeaders, HttpRequest, HttpResponse,
HttpInterceptor, HttpHandler
HttpEvent, HttpRequest, HttpResponse, HttpInterceptor, HttpHandler
} from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { startWith, tap } from 'rxjs/operators';
import { Observable, of, startWith, tap } from 'rxjs';
import { RequestCache } from '../request-cache.service';
import { searchUrl } from '../package-search/package-search.service';

View file

@ -1,39 +1,33 @@
// #docplaster
// #docregion interceptor-providers
/* "Barrel" of Http Interceptors */
import { HTTP_INTERCEPTORS } from '@angular/common/http';
// #enddocregion interceptor-providers
import { CustomJsonInterceptor , CustomJsonParser, JsonParser} from './custom-json-interceptor';
// #docregion 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
import { NoopInterceptor } from './noop-interceptor';
// #enddocregion interceptor-providers
import { TrimNameInterceptor } from './trim-name-interceptor';
import { UploadInterceptor } from './upload-interceptor';
import { RetryInterceptor } from './retry-interceptor';
// #docregion interceptor-providers
/** Http interceptor providers in outside-in order */
/** Array of Http interceptor providers in outside-in order */
export const httpInterceptorProviders = [
// #docregion noop-provider
{ provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },
// #enddocregion noop-provider, interceptor-providers
// #enddocregion interceptor-providers
// #docregion custom-json-interceptor
{ provide: HTTP_INTERCEPTORS, useClass: CustomJsonInterceptor, multi: true },
{ provide: JsonParser, useClass: CustomJsonParser },
// #enddocregion custom-json-interceptor
// #docregion interceptor-providers
{ provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: EnsureHttpsInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: TrimNameInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: UploadInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: CachingInterceptor, multi: true },
// #docregion interceptor-providers
];
// #enddocregion interceptor-providers

View file

@ -1,11 +1,10 @@
import { Injectable } from '@angular/core';
import {
HttpEvent, HttpInterceptor, HttpHandler,
HttpRequest, HttpResponse
HttpInterceptor, HttpHandler, HttpRequest, HttpResponse
} from '@angular/common/http';
// #docregion excerpt
import { finalize, tap } from 'rxjs/operators';
import { finalize, tap } from 'rxjs';
import { MessageService } from '../message.service';
@Injectable()
@ -23,7 +22,7 @@ export class LoggingInterceptor implements HttpInterceptor {
// Succeeds when there is a response; ignore other events
next: (event) => (ok = event instanceof HttpResponse ? 'succeeded' : ''),
// Operation failed; error is an HttpErrorResponse
error: (error) => (ok = 'failed')
error: (_error) => (ok = 'failed')
}),
// Log when response observable either completes or errors
finalize(() => {

View file

@ -1,3 +1,4 @@
// #docregion noop
import { Injectable } from '@angular/core';
import {
HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
@ -8,9 +9,20 @@ import { Observable } from 'rxjs';
/** Pass untouched request through to the next request handler. */
@Injectable()
export class NoopInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
return next.handle(req);
}
}
// #enddocregion noop
// #docregion noop-provider
import { Provider } from '@angular/core';
// Injection token for the Http Interceptors multi-provider
import { HTTP_INTERCEPTORS } from '@angular/common/http';
/** Provider for the Noop Interceptor. */
export const noopInterceptorProvider: Provider =
{ provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true };
// #enddocregion noop-provider

View file

@ -1,9 +1,12 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MessageService } from '../message.service';
@Component({
standalone: true,
selector: 'app-messages',
templateUrl: './messages.component.html',
imports: [ CommonModule ],
styleUrls: ['./messages.component.css']
})
export class MessagesComponent {

View file

@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Observable, Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
@ -6,8 +7,10 @@ import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import { NpmPackageInfo, PackageSearchService } from './package-search.service';
@Component({
standalone: true,
selector: 'app-package-search',
templateUrl: './package-search.component.html',
imports: [ CommonModule ],
styles: ['input { margin-bottom: .5rem; }'],
providers: [ PackageSearchService ]
})

View file

@ -1,9 +1,14 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { UploaderService } from './uploader.service';
@Component({
standalone: true,
selector: 'app-uploader',
templateUrl: './uploader.component.html',
imports: [ CommonModule, FormsModule ],
styles: ['input[type=file] { font-size: 1.2rem; margin-top: 1rem; display: block; }'],
providers: [ UploaderService ]
})

View file

@ -1,7 +1,74 @@
// #docregion
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
// #docplaster
// #docregion sketch
import { bootstrapApplication } from '@angular/platform-browser';
// #enddocregion sketch
import { provideProtractorTestingSupport } from '@angular/platform-browser';
// #docregion sketch
import { HttpClientModule } from '@angular/common/http';
import { importProvidersFrom } from '@angular/core';
// #enddocregion sketch
import { AppModule } from './app/app.module';
import { HttpClientJsonpModule } from '@angular/common/http';
import { HttpClientXsrfModule } from '@angular/common/http';
import { httpInterceptorProviders } from './app/http-interceptors/index';
import { noopInterceptorProvider } from './app/http-interceptors/noop-interceptor';
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
// #region example helper services; not shown in docs
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './app/in-memory-data.service';
import { AuthService } from './app/auth.service';
import { HttpErrorHandler } from './app/http-error-handler.service';
import { MessageService } from './app/message.service';
import { RequestCache, RequestCacheWithMap } from './app/request-cache.service';
// #endregion example helper services; not shown in docs
// #docregion sketch
import {AppComponent} from './app/app.component';
// #docregion interceptor-providers, jsonp, noop-provider, xsrf
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(HttpClientModule),
// #enddocregion interceptor-providers, jsonp, noop-provider, sketch, xsrf
// #docregion jsonp
importProvidersFrom(HttpClientJsonpModule),
// #enddocregion jsonp
// #docregion noop-provider
noopInterceptorProvider,
// #enddocregion noop-provider
// #docregion interceptor-providers
httpInterceptorProviders,
// #enddocregion interceptor-providers
// #docregion xsrf
importProvidersFrom(
HttpClientXsrfModule.withOptions({
cookieName: 'My-Xsrf-Cookie',
headerName: 'My-Xsrf-Header',
})
),
// #enddocregion xsrf
AuthService,
HttpErrorHandler,
MessageService,
{ provide: RequestCache, useClass: RequestCacheWithMap },
importProvidersFrom(
// The HttpClientInMemoryWebApiModule module intercepts HTTP requests
// and returns simulated server responses.
// Remove it when a real server is ready to receive requests.
HttpClientInMemoryWebApiModule.forRoot(
InMemoryDataService, {
dataEncapsulation: false,
passThruUnknownUrl: true,
put204: false // return entity after PUT/update
}
)
),
provideProtractorTestingSupport(), // essential for e2e testing
// #docregion interceptor-providers, jsonp, noop-provider, sketch, xsrf
]
});
// #enddocregion interceptor-providers, jsonp, noop-provider, sketch, xsrf

View file

@ -50,11 +50,4 @@ The following example shows how to pipe a failed request to the `retry()` operat
<code-example header="app/config/config.service.ts (getConfig with retry)" path="http/src/app/config/config.service.ts" region="getConfig"></code-example>
## Sending data to a server
In addition to fetching data from a server, `HttpClient` supports other HTTP methods such as PUT, POST, and DELETE, which you can use to modify the remote data.
The sample app for this guide includes an abridged version of the "Tour of Heroes" example that fetches heroes and enables users to add, delete, and update them.
The following sections show examples of the data-update methods from the sample's `HeroesService`.
@reviewed 2023-08-14
@reviewed 2023-08-29

View file

@ -14,7 +14,7 @@ To implement an interceptor, declare a class that implements the `intercept()` m
Here is a do-nothing `noop` interceptor that passes the request through without touching it:
<code-example header="app/http-interceptors/noop-interceptor.ts" path="http/src/app/http-interceptors/noop-interceptor.ts"></code-example>
<code-example header="app/http-interceptors/noop-interceptor.ts" path="http/src/app/http-interceptors/noop-interceptor.ts" region="noop"></code-example>
The `intercept` method transforms a request into an `Observable` that eventually returns the HTTP response.
In this sense, each interceptor is fully capable of handling the request entirely by itself.
@ -46,42 +46,49 @@ This is a common middleware pattern found in frameworks such as Express.js.
## Provide the interceptor
The `NoopInterceptor` is a service managed by Angular's [dependency injection (DI)](guide/dependency-injection) system.
Like other services, you must provide the interceptor class before the app can use it.
The `NoopInterceptor` is like a service managed by Angular's [dependency injection (DI)](guide/dependency-injection) system.
As with other services, you must provide the interceptor class before the app can use it.
Because interceptors are optional dependencies of the `HttpClient` service, you must provide them in the same injector or a parent of the injector that provides `HttpClient`.
Interceptors provided *after* DI creates the `HttpClient` are ignored.
This app provides `HttpClient` in the app's root injector, as a side-effect of importing the `HttpClientModule` in `AppModule`.
You should provide interceptors in `AppModule` as well.
After importing the `HTTP_INTERCEPTORS` injection token from `@angular/common/http`, write the `NoopInterceptor` provider like this:
<code-example path="http/src/app/http-interceptors/index.ts" region="noop-provider"></code-example>
Write a provider for it like this one:
<code-example path="http/src/app/http-interceptors/noop-interceptor.ts" region="noop-provider"></code-example>
Notice the `multi: true` option.
This required setting tells Angular that `HTTP_INTERCEPTORS` is a token for a *multiprovider* that injects an array of values, rather than a single value.
You *could* add this provider directly to the providers array of the `AppModule`.
However, it's rather verbose and there's a good chance that you'll create more interceptors and provide them in the same way.
Because interceptors are optional dependencies of the `HttpClient` service, you must provide them in the same injector or a parent of the injector that provides `HttpClient`.
Interceptors provided *after* DI creates the `HttpClient` are ignored.
This app provides `HttpClient` in the app's root injector by adding the `HttpClientModule` to the `providers` array of the `boostrapApplication()` in `main.ts`.
You should provide interceptors there as well.
<code-example path="http/src/main.ts" region="noop-provider"></code-example>
## Providing many interceptors
There's a good chance that you'll create more interceptors.
You *could* add each provider to the `providers` array of the `boostrapApplication()` as you did for the `NoopInterceptor`.
That's rather verbose and there's a good chance that you'll make a bookkeeping mistake trying to remember to add each one.
You must also pay [close attention to the order](#interceptor-order) in which you provide these interceptors.
Consider creating a "barrel" file that gathers all the interceptor providers into an `httpInterceptorProviders` array, starting with this first one, the `NoopInterceptor`.
Consider creating a "barrel" file that gathers _all the interceptor providers_ into a single `httpInterceptorProviders` array.
<code-example header="app/http-interceptors/index.ts" path="http/src/app/http-interceptors/index.ts" region="interceptor-providers"></code-example>
Then import and add it to the `AppModule` `providers array` like this:
<code-example header="app/app.module.ts (interceptor providers)" path="http/src/app/app.module.ts" region="interceptor-providers"></code-example>
As you create new interceptors, add them to the `httpInterceptorProviders` array and you won't have to revisit the `AppModule`.
<div class="alert is-helpful">
There are many more interceptors in the complete sample code.
These interceptors are defined in the complete sample code.
</div>
Then import this array and add it to the `bootstrapApplication()` `providers` in `main.ts` like this:
<code-example header="main.ts (interceptor providers)" path="http/src/main.ts" region="interceptor-providers"></code-example>
As you create new interceptors, add them to the `httpInterceptorProviders` array and you won't have to revisit `main.ts`.
## Interceptor order
Angular applies interceptors in the order that you provide them.
@ -186,4 +193,4 @@ newReq = req.clone({ body: null }); // clear the body
</code-example>
@reviewed 2023-03-16
@reviewed 2023-08-16

View file

@ -53,7 +53,7 @@ Here is a custom JsonParser that has a special date reviver.
<code-example header="app/http-interceptors/custom-json-interceptor.ts" path="http/src/app/http-interceptors/custom-json-interceptor.ts" region="custom-json-parser"></code-example>
You provide the `CustomParser` along with the `CustomJsonInterceptor`.
Finally, provide the `CustomParser` along with the `CustomJsonInterceptor` in that same `httpInterceptorProviders` array.
<code-example header="app/http-interceptors/index.ts" path="http/src/app/http-interceptors/index.ts" region="custom-json-interceptor"></code-example>

View file

@ -7,25 +7,19 @@ Apps can use the `HttpClient` to make [JSONP](https://en.wikipedia.org/wiki/JSON
Angular JSONP requests return an `Observable`.
Follow the pattern for subscribing to observables and use the RxJS `map` operator to transform the response before using the [async pipe](api/common/AsyncPipe) to manage the results.
In Angular, use JSONP by including `HttpClientJsonpModule` in the `NgModule` imports.
In the following example, the `searchHeroes()` method uses a JSONP request to query for heroes whose names contain the search term.
Enable JSONP by providing the `HttpClientJsonpModule` in the `bootstrapApplication` providers array in `main.ts` like this:
<code-example format="typescript" language="typescript">
<code-example path="http/src/main.ts" region="jsonp"></code-example>
/* GET heroes whose name contains search term */
searchHeroes(term: string): Observable {
term = term.trim();
const heroesURL = `&dollar;{this.heroesURL}?&dollar;{term}`;
return this.http.jsonp(heroesUrl, 'callback').pipe(
catchError(this.handleError('searchHeroes', [])) // then handle the error
);
}
In the following example, the `searchHeroesJsonp()` method uses a JSONP request to query for heroes whose names contain the search term acquired from the user.
<code-example path="http/src/app/heroes/heroes.service.ts" region="searchHeroesJsonp">
</code-example>
This request passes the `heroesURL` as the first parameter and the callback function name as the second parameter.
The response is wrapped in the callback function, which takes the observables returned by the JSONP method and pipes them through to the error handler.
This request passes the `heroesUrl` with the search term as the first parameter and the standard callback function name, `callback`, as the second parameter.
You may have to `map` the Observable response from the `http.jsonp` method to the intended data type
as this example does with `jsonpResultToHeroes`.
## Request non-JSON data
@ -44,4 +38,4 @@ A `download()` method in the `DownloaderComponent` initiates the request by subs
<a id="error-handling"></a>
@reviewed 2022-11-03
@reviewed 2023-08-17

View file

@ -1,10 +1,10 @@
# HTTP: Request data from a server
Use the [`HttpClient.get()`](api/common/http/HttpClient#get) method to fetch data from a server.
The asynchronous method sends an HTTP request, and returns an Observable that emits the requested data when the response is received.
The return type varies based on the `observe` and `responseType` values that you pass to the call.
The `get()` method takes two arguments; the endpoint URL from which to fetch, and an *options* object that is used to configure the request.
This asynchronous method sends an HTTP request, and returns an [Observable](guide/observables-in-angular) that emits the requested data when the response is received.
The `get(url, options)` method takes two arguments; the string endpoint URL from which to fetch, and an *optional options* object to configure the request.
<code-example format="typescript" language="typescript">
@ -22,7 +22,9 @@ options: {
Important options include the *observe* and *responseType* properties.
* The *observe* option specifies how much of the response to return
* The *responseType* option specifies the format in which to return data
* The *responseType* option specifies the desired format of the returned data
To better understand the `observe` and `responseType` option types, [see below](#string-union-types).
<div class="alert is-helpful">
@ -39,26 +41,40 @@ In the `ConfigService` example, the app needs a configuration file on the server
<code-example header="assets/config.json" path="http/src/assets/config.json"></code-example>
To fetch this kind of data, the `get()` call needs the following options: `{observe: 'body', responseType: 'json'}`.
These are the default values for those options, so the following examples do not pass the options object.
*These are the **default values** for those options*, so most `get()` calls - and most of the following examples - do not pass the options object.
Later sections show some of the additional option possibilities.
<a id="config-service"></a>
The example conforms to the best practices for creating scalable solutions by defining a re-usable [injectable service](guide/glossary#service "service definition") to perform the data-handling functionality.
In addition to fetching data, the service can post-process the data, add error handling, and add retry logic.
### Handle data access in a service class
The `ConfigService` fetches this file using the `HttpClient.get()` method.
The example conforms to the best practice for maintainable solutions by isolating the data-access functionality in a re-usable [injectable service](guide/glossary#service "service definition") separate from the component.
The `ConfigService` fetches the JSON file using the `HttpClient.get()` method.
<code-example header="app/config/config.service.ts (getConfig v.1)" path="http/src/app/config/config.service.ts" region="getConfig_1"></code-example>
The `ConfigComponent` injects the `ConfigService` and calls the `getConfig` service method.
Notice that `get` was called
* without an *options* value because the server endpoint returns JSON and JSON is the default data format.
* with a generic, `Config`, that indicates the data return type; you'll [learn why shortly](#typed-response).
Because the service method returns an `Observable` of configuration data, the component *subscribes* to the method's return value.
The subscription callback performs minimal post-processing.
It copies the data fields into the component's `config` object, which is data-bound in the component template for display.
In addition to fetching data, the service can post-process the data,
[add error handling](guide/http-handle-request-errors),
and add retry logic.
### Present the data in the component
The `ConfigComponent` injects the `ConfigService` in its constructor and offers a `showConfig` method, which calls the service's `getConfig` method.
<code-example header="app/config/config.component.ts (showConfig v.1)" path="http/src/app/config/config.component.ts" region="v1"></code-example>
Because the service's `getConfig` method returns an `Observable` of configuration data, the component *subscribes* to the method's return value.
If you didn't subscribe, the service would not have issued an HTTP request and there would be no config data to display. You will [understand why shortly](#always-subscribe).
This example subscription callback performs minimal post-processing.
It copies the data fields into the component's `config` object, which is data-bound in the component template for display.
<a id="always-subscribe"></a>
## Starting the request
@ -69,7 +85,9 @@ This is true for *all* `HttpClient` *methods*.
<div class="alert is-helpful">
You should always unsubscribe from an observable when a component is destroyed.
In general, you should unsubscribe from an observable when a component is destroyed.
You don't have to unsubscribe from `HttpClient` observables because they unsubscribe automatically after the server request responds or times out. Most developers choose not to unsubscribe. None of this guide's examples unsubscribe.
</div>
@ -113,45 +131,60 @@ It is up to the server to ensure that the type specified by the server API is re
</div>
To specify the response object type, first define an interface with the required properties.
Suppose you made the `get` call without specifying the return type like this:
<code-example header="Config Service - get without result type (not so good)" path="http/src/app/config/config.service.ts" region="untyped_response">
</code-example>
The return type would be `Object`, To access its properties you would have to explicitly convert them with `as any` like this:
<code-example header="Config Component - without result type (not so good)" path="http/src/app/config/config.component.ts" region="untyped_response">
</code-example>
It's safer and less clumsy if the returned object has the desired type.
Begin by defining an interface with the required properties.
Use an interface rather than a class, because the response is a plain object that cannot be automatically converted to an instance of a class.
<code-example path="http/src/app/config/config.service.ts" region="config-interface"></code-example>
Next, specify that interface as the `HttpClient.get()` call's type parameter in the service.
Now, specify that interface as the `HttpClient.get()` call's type parameter in the service.
<code-example header="app/config/config.service.ts (getConfig v.2)" path="http/src/app/config/config.service.ts" region="getConfig_2"></code-example>
<div class="alert is-helpful">
When you pass an interface as a type parameter to the `HttpClient.get()` method, use the [RxJS `map` operator](guide/rx-library#operators) to transform the response data as needed by the UI.
You can then pass the transformed data to the [async pipe](api/common/AsyncPipe).
</div>
<code-example header="Config Service - get with result type (better)" path="http/src/app/config/config.service.ts" region="getConfig_2"></code-example>
The callback in the updated component method receives a typed data object, which is easier and safer to consume:
<code-example header="app/config/config.component.ts (showConfig v.2)" path="http/src/app/config/config.component.ts" region="v2"></code-example>
To access properties that are defined in an interface, you must explicitly convert the plain object you get from the JSON to the required response type.
For example, the following `subscribe` callback receives `data` as an Object, and then type-casts it in order to access the properties.
<code-example format="typescript" language="typescript">
.subscribe(data =&gt; this.config = {
heroesUrl: (data as any).heroesUrl,
textfile: (data as any).textfile,
});
<code-example header="Config Component - with typed response" path="http/src/app/config/config.component.ts" region="typed_response">
</code-example>
You can go a step further and clone the result directly into the component's `config` property with [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#description).
<code-example header="Config Component - with destructured assignment" path="http/src/app/config/config.component.ts" region="v2"></code-example>
## Reading the full response
In the previous example, the call to `HttpClient.get()` did not specify any options.
By default, it returned the JSON data contained in the response body.
You might need more information about the transaction than is contained in the response body.
Sometimes servers return special headers or status codes to indicate certain conditions that are important to the application workflow.
Tell `HttpClient` that you want the full response with the `observe` option of the `get()` method:
<code-example path="http/src/app/config/config.service.ts" region="getConfigResponse"></code-example>
Now `HttpClient.get()` returns an `Observable` of type `HttpResponse` rather than just the JSON data contained in the body.
The component's `showConfigResponse()` method displays the response headers as well as the configuration:
<code-example header="app/config/config.component.ts (showConfigResponse)" path="http/src/app/config/config.component.ts" region="showConfigResponse"></code-example>
As you can see, the response object has a `body` property of the correct type.
<a id="string-union-types"></a>
## The `observe` and `responseType` options
<div class="callout is-important">
<header><code>observe</code> and <code>response</code> types</header>
The types of the `observe` and `response` options are *string unions*, rather than plain strings.
The types of the `observe` and `responseType` options are *string unions*, rather than plain strings.
<code-example format="typescript" language="typescript">
@ -198,24 +231,4 @@ client.get('/foo', options);
</div>
## Reading the full response
In the previous example, the call to `HttpClient.get()` did not specify any options.
By default, it returned the JSON data contained in the response body.
You might need more information about the transaction than is contained in the response body.
Sometimes servers return special headers or status codes to indicate certain conditions that are important to the application workflow.
Tell `HttpClient` that you want the full response with the `observe` option of the `get()` method:
<code-example path="http/src/app/config/config.service.ts" region="getConfigResponse"></code-example>
Now `HttpClient.get()` returns an `Observable` of type `HttpResponse` rather than just the JSON data contained in the body.
The component's `showConfigResponse()` method displays the response headers as well as the configuration:
<code-example header="app/config/config.component.ts (showConfigResponse)" path="http/src/app/config/config.component.ts" region="showConfigResponse"></code-example>
As you can see, the response object has a `body` property of the correct type.
@reviewed 2023-02-27
@reviewed 2023-08-18

View file

@ -28,8 +28,10 @@ Failing to do so renders Angular's default protection ineffective.
If your backend service uses different names for the XSRF token cookie or header, use `HttpClientXsrfModule.withOptions()` to override the defaults.
<code-example path="http/src/app/app.module.ts" region="xsrf"></code-example>
Add it to the `bootstrapApplication()` `providers` array in `main.ts` as follows:
<code-example path="http/src/main.ts" region="xsrf"></code-example>
<a id="testing-requests"></a>
@reviewed 2022-11-14
@reviewed 2023-08-16

View file

@ -47,9 +47,8 @@ Calling the `subscribe()` method *executes* the observable, which is what initia
You must call `subscribe()` or nothing happens.
Just calling `HeroesService.deleteHero()` does not initiate the DELETE request.
</div>
<code-example path="http/src/app/heroes/heroes.component.ts" region="delete-hero-no-subscribe"></code-example>
</div>
## Make a PUT request

View file

@ -1,36 +1,43 @@
# HTTP Server communication
<div class="callout is-critical">
<header>Marked for archiving</header>
To ensure that you have the best experience possible, this topic is marked for archiving until we determine that it clearly conveys the most accurate information possible.
In the meantime, this topic might be helpful: [Understanding HTTP](guide/understanding-communicating-with-http).
If you think this content should not be archived, please file a [GitHub issue](https://github.com/angular/angular/issues/new?template=3-docs-bug.md).
</div>
Most front-end applications need to communicate with a server over the HTTP protocol, to download or upload data and access other back-end services.
## Setup for server communication
Before you can use `HttpClient`, you need to import the Angular `HttpClientModule`.
Most apps do so in the root `AppModule`.
<code-example header="app/app.module.ts (excerpt)" path="http/src/app/app.module.ts" region="sketch"></code-example>
You can then inject the `HttpClient` service as a dependency of an application class, as shown in the following `ConfigService` example.
<code-example header="app/config/config.service.ts (excerpt)" path="http/src/app/config/config.service.ts" region="proto"></code-example>
The `HttpClient` service makes use of [observables](guide/glossary#observable "Observable definition") for all transactions.
You must import the RxJS observable and operator symbols that appear in the example snippets.
These `ConfigService` imports are typical.
<code-example header="app/config/config.service.ts (RxJS imports)" path="http/src/app/config/config.service.ts" region="rxjs-imports"></code-example>
<div class="alert is-helpful">
You can run the <live-example></live-example> that accompanies this guide.
You can run the <live-example name="http"></live-example> that accompanies this guide.
The sample app does not require a data server.
It relies on the [Angular *in-memory-web-api*](https://github.com/angular/angular/tree/main/packages/misc/angular-in-memory-web-api), which replaces the *HttpClient* module's `HttpBackend`.
The replacement service simulates the behavior of a REST-like backend.
Look at the `AppModule` *imports* to see how it is configured.
Look at the `bootstrapApplication()` method in `main.ts` to see how it is configured.
</div>
## Setup for server communication
Before you can use `HttpClient`, you need to provide the Angular `HttpClientModule` so that it is available for [dependency injection](guide/dependency-injection) into the classes that need it.
Most developers provide the `HttpClientModule` when initializing the app with `bootstrapApplication` in `main.ts` as shown in this example:
<code-example header="main.ts (excerpt)" path="http/src/main.ts" region="sketch"></code-example>
You can then inject the `HttpClient` service as a dependency of an application class, as shown in the following `ConfigService` example.
<code-example header="app/config/config.service.ts (excerpt)" path="http/src/app/config/config.service.ts" region="proto"></code-example>
## Requesting data from a server
Use the [`HttpClient.get()`](api/common/http/HttpClient#get) method to fetch data from a server.
@ -251,4 +258,4 @@ The component's `showConfigResponse()` method displays the response headers as w
As you can see, the response object has a `body` property of the correct type.
@reviewed 2023-02-27
@reviewed 2023-08-16

View file

@ -1,30 +1,25 @@
# HTTP: Setup for server communication
Before you can use `HttpClient`, you need to import the Angular `HttpClientModule`.
Most apps do so in the root `AppModule`.
Before you can use `HttpClient`, you must add it to the application's [root dependency injector](guide/dependency-injection).
<code-example header="app/app.module.ts (excerpt)" path="http/src/app/app.module.ts" region="sketch"></code-example>
Most apps do so in the `providers` array of `bootstrapApplication()` in `main.ts`.
<code-example header="main.ts (excerpt)" path="http/src/main.ts" region="sketch"></code-example>
You can then inject the `HttpClient` service as a dependency of an application class, as shown in the following `ConfigService` example.
<code-example header="app/config/config.service.ts (excerpt)" path="http/src/app/config/config.service.ts" region="proto"></code-example>
The `HttpClient` service makes use of [observables](guide/glossary#observable "Observable definition") for all transactions.
You must import the RxJS observable and operator symbols that appear in the example snippets.
These `ConfigService` imports are typical.
<code-example header="app/config/config.service.ts (RxJS imports)" path="http/src/app/config/config.service.ts" region="rxjs-imports"></code-example>
<div class="alert is-helpful">
You can run the <live-example></live-example> that accompanies this guide.
You can run the <live-example name="http"></live-example> that accompanies this guide.
The sample app does not require a data server.
It relies on the [Angular *in-memory-web-api*](https://github.com/angular/angular/tree/main/packages/misc/angular-in-memory-web-api), which replaces the *HttpClient* module's `HttpBackend`.
The replacement service simulates the behavior of a REST-like backend.
Look at the `AppModule` *imports* to see how it is configured.
Look at the `bootstrapApplication()` method in `main.ts` to see how it is configured.
</div>
@reviewed 2022-11-03
@reviewed 2023-08-16

View file

@ -23,6 +23,6 @@ The HTTP client service offers the following major features.
## What's next
* [Setup for server communication](guide/http-server-communication)
* [Setup for server communication](guide/http-setup-server-communication)
@reviewed 2023-03-14
@reviewed 2023-08-16

View file

@ -449,11 +449,6 @@
"title": "Setup for server communication",
"tooltip": "Setup a server communication for HTTP"
},
{
"url": "guide/http-server-communication",
"title": "Communicating with backend services",
"tooltip": "Understanding communicating with backend services using HTTP"
},
{
"url": "guide/http-request-data-from-server",
"title": "Request data from a server",