From 8ef5cc680d56a7af019cb72b8b8c33db84e90f6c Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Wed, 16 Aug 2023 22:11:14 -0700 Subject: [PATCH] docs: Migrate HttpClient guide and its code examples (`/http`) to standalone (#51400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **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 --- .../examples/http/src/app/app.component.ts | 19 +++ .../examples/http/src/app/app.module.ts | 89 ------------ .../http/src/app/config/config.component.ts | 41 ++++-- .../http/src/app/config/config.service.ts | 12 +- .../app/downloader/downloader.component.ts | 3 + .../http/src/app/heroes/heroes.component.ts | 5 +- .../http/src/app/heroes/heroes.service.ts | 27 +++- .../app/http-interceptors/auth-interceptor.ts | 2 +- .../http-interceptors/caching-interceptor.ts | 6 +- .../http/src/app/http-interceptors/index.ts | 20 +-- .../http-interceptors/logging-interceptor.ts | 7 +- .../app/http-interceptors/noop-interceptor.ts | 14 +- .../src/app/messages/messages.component.ts | 3 + .../package-search.component.ts | 3 + .../src/app/uploader/uploader.component.ts | 5 + aio/content/examples/http/src/main.ts | 77 +++++++++- .../guide/http-handle-request-errors.md | 9 +- .../http-intercept-requests-and-responses.md | 53 ++++--- .../guide/http-interceptor-use-cases.md | 2 +- aio/content/guide/http-make-jsonp-request.md | 24 ++-- .../guide/http-request-data-from-server.md | 135 ++++++++++-------- .../guide/http-security-xsrf-protection.md | 6 +- aio/content/guide/http-send-data-to-server.md | 3 +- .../guide/http-server-communication.md | 47 +++--- .../guide/http-setup-server-communication.md | 19 +-- .../understanding-communicating-with-http.md | 4 +- aio/content/navigation.json | 5 - 27 files changed, 352 insertions(+), 288 deletions(-) delete mode 100644 aio/content/examples/http/src/app/app.module.ts diff --git a/aio/content/examples/http/src/app/app.component.ts b/aio/content/examples/http/src/app/app.component.ts index 09cd2020eee..ec3c00a0bdb 100644 --- a/aio/content/examples/http/src/app/app.component.ts +++ b/aio/content/examples/http/src/app/app.component.ts @@ -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 { diff --git a/aio/content/examples/http/src/app/app.module.ts b/aio/content/examples/http/src/app/app.module.ts deleted file mode 100644 index d9a9a5934b1..00000000000 --- a/aio/content/examples/http/src/app/app.module.ts +++ /dev/null @@ -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 diff --git a/aio/content/examples/http/src/app/config/config.component.ts b/aio/content/examples/http/src/app/config/config.component.ts index 4bfe51b38df..c3ebcc718c0 100644 --- a/aio/content/examples/http/src/app/config/config.component.ts +++ b/aio/content/examples/http/src/app/config/config.component.ts @@ -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 + diff --git a/aio/content/examples/http/src/app/config/config.service.ts b/aio/content/examples/http/src/app/config/config.service.ts index c974e129677..c2e67b98344 100644 --- a/aio/content/examples/http/src/app/config/config.service.ts +++ b/aio/content/examples/http/src/app/config/config.service.ts @@ -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> { return this.http.get( diff --git a/aio/content/examples/http/src/app/downloader/downloader.component.ts b/aio/content/examples/http/src/app/downloader/downloader.component.ts index 2efee0748aa..c8ef1913e0d 100644 --- a/aio/content/examples/http/src/app/downloader/downloader.component.ts +++ b/aio/content/examples/http/src/app/downloader/downloader.component.ts @@ -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 { diff --git a/aio/content/examples/http/src/app/heroes/heroes.component.ts b/aio/content/examples/http/src/app/heroes/heroes.component.ts index 79124cfeebb..9bea8077bb0 100644 --- a/aio/content/examples/http/src/app/heroes/heroes.component.ts +++ b/aio/content/examples/http/src/app/heroes/heroes.component.ts @@ -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'] }) diff --git a/aio/content/examples/http/src/app/heroes/heroes.service.ts b/aio/content/examples/http/src/app/heroes/heroes.service.ts index ac31663b92f..8d3ac9a7f95 100644 --- a/aio/content/examples/http/src/app/heroes/heroes.service.ts +++ b/aio/content/examples/http/src/app/heroes/heroes.service.ts @@ -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 { + // #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 { + // #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 diff --git a/aio/content/examples/http/src/app/http-interceptors/auth-interceptor.ts b/aio/content/examples/http/src/app/http-interceptors/auth-interceptor.ts index 85fb760450c..bfd9103f043 100644 --- a/aio/content/examples/http/src/app/http-interceptors/auth-interceptor.ts +++ b/aio/content/examples/http/src/app/http-interceptors/auth-interceptor.ts @@ -1,7 +1,7 @@ // #docplaster import { Injectable } from '@angular/core'; import { - HttpEvent, HttpInterceptor, HttpHandler, HttpRequest + HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; // #docregion diff --git a/aio/content/examples/http/src/app/http-interceptors/caching-interceptor.ts b/aio/content/examples/http/src/app/http-interceptors/caching-interceptor.ts index e9646833855..a0d12b80f47 100644 --- a/aio/content/examples/http/src/app/http-interceptors/caching-interceptor.ts +++ b/aio/content/examples/http/src/app/http-interceptors/caching-interceptor.ts @@ -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'; diff --git a/aio/content/examples/http/src/app/http-interceptors/index.ts b/aio/content/examples/http/src/app/http-interceptors/index.ts index d31d6696e08..68f8d55c0fd 100644 --- a/aio/content/examples/http/src/app/http-interceptors/index.ts +++ b/aio/content/examples/http/src/app/http-interceptors/index.ts @@ -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 diff --git a/aio/content/examples/http/src/app/http-interceptors/logging-interceptor.ts b/aio/content/examples/http/src/app/http-interceptors/logging-interceptor.ts index 7a7f57f7b19..c04ddace602 100644 --- a/aio/content/examples/http/src/app/http-interceptors/logging-interceptor.ts +++ b/aio/content/examples/http/src/app/http-interceptors/logging-interceptor.ts @@ -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(() => { diff --git a/aio/content/examples/http/src/app/http-interceptors/noop-interceptor.ts b/aio/content/examples/http/src/app/http-interceptors/noop-interceptor.ts index 51d40208040..4e6bbb3fddd 100644 --- a/aio/content/examples/http/src/app/http-interceptors/noop-interceptor.ts +++ b/aio/content/examples/http/src/app/http-interceptors/noop-interceptor.ts @@ -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, next: HttpHandler): Observable> { 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 diff --git a/aio/content/examples/http/src/app/messages/messages.component.ts b/aio/content/examples/http/src/app/messages/messages.component.ts index 034c0ac0afc..8eb78671038 100644 --- a/aio/content/examples/http/src/app/messages/messages.component.ts +++ b/aio/content/examples/http/src/app/messages/messages.component.ts @@ -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 { diff --git a/aio/content/examples/http/src/app/package-search/package-search.component.ts b/aio/content/examples/http/src/app/package-search/package-search.component.ts index 8353d99fcaa..4cd2dbd3e8a 100644 --- a/aio/content/examples/http/src/app/package-search/package-search.component.ts +++ b/aio/content/examples/http/src/app/package-search/package-search.component.ts @@ -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 ] }) diff --git a/aio/content/examples/http/src/app/uploader/uploader.component.ts b/aio/content/examples/http/src/app/uploader/uploader.component.ts index 3692f7c517d..32a956aa093 100644 --- a/aio/content/examples/http/src/app/uploader/uploader.component.ts +++ b/aio/content/examples/http/src/app/uploader/uploader.component.ts @@ -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 ] }) diff --git a/aio/content/examples/http/src/main.ts b/aio/content/examples/http/src/main.ts index 49432aa0877..53bca01542a 100644 --- a/aio/content/examples/http/src/main.ts +++ b/aio/content/examples/http/src/main.ts @@ -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 diff --git a/aio/content/guide/http-handle-request-errors.md b/aio/content/guide/http-handle-request-errors.md index 7ec2c8980e9..9cf1f3f296f 100644 --- a/aio/content/guide/http-handle-request-errors.md +++ b/aio/content/guide/http-handle-request-errors.md @@ -50,11 +50,4 @@ The following example shows how to pipe a failed request to the `retry()` operat -## 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 diff --git a/aio/content/guide/http-intercept-requests-and-responses.md b/aio/content/guide/http-intercept-requests-and-responses.md index eeebc2ab581..247dbba19ed 100644 --- a/aio/content/guide/http-intercept-requests-and-responses.md +++ b/aio/content/guide/http-intercept-requests-and-responses.md @@ -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: - + 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: - - +Write a provider for it like this one: + 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. + + + +## 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. -Then import and add it to the `AppModule` `providers array` like this: - - - -As you create new interceptors, add them to the `httpInterceptorProviders` array and you won't have to revisit the `AppModule`. -
-There are many more interceptors in the complete sample code. +These interceptors are defined in the complete sample code.
+Then import this array and add it to the `bootstrapApplication()` `providers` in `main.ts` like this: + + + +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 -@reviewed 2023-03-16 +@reviewed 2023-08-16 diff --git a/aio/content/guide/http-interceptor-use-cases.md b/aio/content/guide/http-interceptor-use-cases.md index 60652b0fea1..32a29ed11e0 100644 --- a/aio/content/guide/http-interceptor-use-cases.md +++ b/aio/content/guide/http-interceptor-use-cases.md @@ -53,7 +53,7 @@ Here is a custom JsonParser that has a special date reviver. -You provide the `CustomParser` along with the `CustomJsonInterceptor`. +Finally, provide the `CustomParser` along with the `CustomJsonInterceptor` in that same `httpInterceptorProviders` array. diff --git a/aio/content/guide/http-make-jsonp-request.md b/aio/content/guide/http-make-jsonp-request.md index 594caef6dbf..5f25aaec194 100644 --- a/aio/content/guide/http-make-jsonp-request.md +++ b/aio/content/guide/http-make-jsonp-request.md @@ -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: - + -/* GET heroes whose name contains search term */ -searchHeroes(term: string): Observable { - term = term.trim(); - - const heroesURL = `${this.heroesURL}?${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. + -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 -@reviewed 2022-11-03 +@reviewed 2023-08-17 diff --git a/aio/content/guide/http-request-data-from-server.md b/aio/content/guide/http-request-data-from-server.md index cb1200520a9..22894ff4afd 100644 --- a/aio/content/guide/http-request-data-from-server.md +++ b/aio/content/guide/http-request-data-from-server.md @@ -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. @@ -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).
@@ -39,26 +41,40 @@ In the `ConfigService` example, the app needs a configuration file on the server 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. -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. -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. +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. + ## Starting the request @@ -69,7 +85,9 @@ This is true for *all* `HttpClient` *methods*.
-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.
@@ -113,45 +131,60 @@ It is up to the server to ensure that the type specified by the server API is re
-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: + + + +The return type would be `Object`, To access its properties you would have to explicitly convert them with `as any` like this: + + + + +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. -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. - - -
- -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). - -
+ The callback in the updated component method receives a typed data object, which is easier and safer to consume: - - -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. - - - -.subscribe(data => this.config = { - heroesUrl: (data as any).heroesUrl, - textfile: (data as any).textfile, -}); - + +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). + + + + +## 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: + + + +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: + + + +As you can see, the response object has a `body` property of the correct type. + +## The `observe` and `responseType` options -
- -
observe and response types
- -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. @@ -198,24 +231,4 @@ client.get('/foo', options);
-## 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: - - - -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: - - - -As you can see, the response object has a `body` property of the correct type. - -@reviewed 2023-02-27 +@reviewed 2023-08-18 diff --git a/aio/content/guide/http-security-xsrf-protection.md b/aio/content/guide/http-security-xsrf-protection.md index eb2ca75f82a..cecaf1a2484 100644 --- a/aio/content/guide/http-security-xsrf-protection.md +++ b/aio/content/guide/http-security-xsrf-protection.md @@ -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. - +Add it to the `bootstrapApplication()` `providers` array in `main.ts` as follows: + + -@reviewed 2022-11-14 +@reviewed 2023-08-16 diff --git a/aio/content/guide/http-send-data-to-server.md b/aio/content/guide/http-send-data-to-server.md index a7281768db7..0743e0b3bfa 100644 --- a/aio/content/guide/http-send-data-to-server.md +++ b/aio/content/guide/http-send-data-to-server.md @@ -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. - - + ## Make a PUT request diff --git a/aio/content/guide/http-server-communication.md b/aio/content/guide/http-server-communication.md index 4244ae705ad..beb553e0a77 100644 --- a/aio/content/guide/http-server-communication.md +++ b/aio/content/guide/http-server-communication.md @@ -1,36 +1,43 @@ # HTTP Server communication +
+ +
Marked for archiving
+ +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). + +
+ 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`. - - - -You can then inject the `HttpClient` service as a dependency of an application class, as shown in the following `ConfigService` 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. - - -
-You can run the that accompanies this guide. +You can run the 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.
+## 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: + + + +You can then inject the `HttpClient` service as a dependency of an application class, as shown in the following `ConfigService` 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 diff --git a/aio/content/guide/http-setup-server-communication.md b/aio/content/guide/http-setup-server-communication.md index 25b3cfe2db2..75a5d43ff98 100644 --- a/aio/content/guide/http-setup-server-communication.md +++ b/aio/content/guide/http-setup-server-communication.md @@ -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). - +Most apps do so in the `providers` array of `bootstrapApplication()` in `main.ts`. + + You can then inject the `HttpClient` service as a dependency of an application class, as shown in the following `ConfigService` 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. - - -
-You can run the that accompanies this guide. +You can run the 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.
-@reviewed 2022-11-03 +@reviewed 2023-08-16 diff --git a/aio/content/guide/understanding-communicating-with-http.md b/aio/content/guide/understanding-communicating-with-http.md index 47d1ef7f1f6..84a42dcf23c 100644 --- a/aio/content/guide/understanding-communicating-with-http.md +++ b/aio/content/guide/understanding-communicating-with-http.md @@ -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 diff --git a/aio/content/navigation.json b/aio/content/navigation.json index 3e2969779d4..ab004319825 100644 --- a/aio/content/navigation.json +++ b/aio/content/navigation.json @@ -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",