Most front-end applications communicate with backend services over the HTTP protocol. Modern browsers support two different APIs for making HTTP requests: the XMLHttpRequest interface and the fetch() API.

The sample app does not require a data server. It relies on the Angular in-memory-web-api, which replaces the HttpClient module's HttpBackend. The replacement service simulates the behavior of a REST-like backend.

Because the service method returns an Observable of configuration data, the component subscribes to the method's return value. The subscription callback copies the data fields into the component's config object, which is data-bound in the component template for display.

That's why it is a best practice to separate presentation of data from data access by encapsulating data access in a separate service and delegating to that service in the component, even in simple cases like this one.

Or something could go wrong on the client-side such as a network error that prevents the request from completing successfully or an exception thrown in an RxJS operator. These errors produce JavaScript ErrorEvent objects.

The RxJS library offers several retry operators that are worth exploring. The simplest is called retry() and it automatically re-subscribes to a failed Observable a specified number of times. Re-subscribing to the result of an HttpClient method call has the effect of reissuing the HTTP request.

RxJS itself is out-of-scope for this guide. You will find many learning resources on the web. While you can get by with a minimum of RxJS knowledge, you'll want to grow your RxJS skills over time in order to use HttpClient effectively.

Not all APIs return JSON data. In this next example, a DownloaderService method reads a text file from the server and logs the file contents, before returning those contents to the caller as an Observable<string>.

getTextFile(filename:string){// The Observable returned by get() is of type Observable<string>// because a text response was specified.// There's no need to pass a <string> type parameter to get().returnthis.http.get(filename,{responseType:'text'}).pipe(
tap(// Log the result or error
data =>this.log(filename, data),
error =>this.logError(filename, error)));}

Many servers require extra headers for save operations. For example, they may require a "Content-Type" header to explicitly declare the MIME type of the request body. Or perhaps the server requires an authorization token.

The component isn't expecting a result from the delete operation, so it subscribes without a callback. Even though you are not using the result, you still have to subscribe. Calling the subscribe() method executes the observable, which is what initiates the DELETE request.

All observables returned from HttpClient methods are cold by design. Execution of the HTTP request is deferred, allowing you to extend the observable with additional operations such as tap and catchError before anything actually happens.

The (keyup) event binding sends every keystroke to the component's search() method.

如果每次击键都发送一次请求就太昂贵了。 最好能等到用户停止输入时才发送请求。 使用 RxJS 的操作符就能轻易实现它，参见下面的代码片段：

Sending a request for every keystroke could be expensive. It's better to wait until the user stops typing and then send a request. That's easy to implement with RxJS operators, as shown in this excerpt.

The searchText$ is the sequence of search-box values coming from the user. It's defined as an RxJS Subject, which means it is a multicasting Observable that can also produce values for itself by calling next(value), as happens in the search() method.

HTTP Interception is a major feature of @angular/common/http. With interception, you declare interceptors that inspect and transform HTTP requests from your application to the server. The same interceptors may also inspect and transform the server's responses on their way back to the application. Multiple interceptors form a forward-and-backward chain of request/response handlers.

拦截器可以用一种常规的、标准的方式对每一次 HTTP 的请求/响应任务执行从认证到记日志等很多种隐式任务。

Interceptors can perform a variety of implicit tasks, from authentication to logging, in a routine, standard way, for every HTTP request/response.

Like intercept(), the handle() method transforms an HTTP request into an Observable of HttpEventswhich ultimately include the server's response. The intercept() method could inspect that observable and alter it before returning it to the caller.

这个无操作的拦截器，会直接使用原始的请求调用 next.handle()，并返回它返回的可观察对象，而不做任何后续处理。

This no-op interceptor simply calls next.handle() with the original request and returns the observable without doing a thing.

The next object represents the next interceptor in the chain of interceptors. The final next in the chain is the HttpClient backend handler that sends the request to the server and receives the server's response.

Most interceptors call next.handle() so that the request flows through to the next interceptor and, eventually, the backend handler. An interceptor could skip calling next.handle(), short-circuit the chain, and return its own Observablewith an artificial server response.

这是一种常见的中间件模式，在像 Express.js 这样的框架中也会找到它。

This is a common middleware pattern found in frameworks such as Express.js.

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.

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. You must also pay close attention to the order in which you provide these interceptors.

That's because interceptors work at a lower level than those HttpClient methods. A single HTTP request can generate multiple events, including upload and download progress events. The HttpResponse class itself is actually an event, whose type is HttpEventType.HttpResponseEvent.

很多拦截器只关心发出的请求，而对 next.handle() 返回的事件流不会做任何修改。

Many interceptors are only concerned with the outgoing request and simply return the event stream from next.handle() without modifying it.

But interceptors that examine and modify the response from next.handle() will see all of these events. Your interceptor should return every event untouched unless it has a compelling reason to do otherwise.

They are immutable for a good reason: the app may retry a request several times before it succeeds, which means that the interceptor chain may re-process the same request multiple times. If an interceptor could modify the original request object, the re-tried operation would start from the modified request rather than the original. Immutability ensures that interceptors see the same request for each try.

To alter the request, clone it first and modify the clone before passing it to next.handle(). You can clone and modify the request in a single step as in this example.

// clone request and replace 'http://' with 'https://' at the same time const secureReq = req.clone({ url: req.url.replace('http://', 'https://') }); // send the cloned, "secure" request to the next handler. return next.handle(secureReq);

app/http-interceptors/ensure-https-interceptor.ts (excerpt)

// clone request and replace 'http://' with 'https://' at the same timeconst secureReq = req.clone({
url: req.url.replace('http://','https://')});// send the cloned, "secure" request to the next handler.returnnext.handle(secureReq);

这个 clone() 方法的哈希型参数允许你在复制出克隆体的同时改变该请求的某些特定属性。

The clone() method's hash argument allows you to mutate specific properties of the request while copying the others.

// copy the body and trim whitespace from the name propertyconst newBody ={...body, name: body.name.trim()};// clone request and set its bodyconst newReq = req.clone({ body: newBody });// send the cloned request to the next handler.returnnext.handle(newReq);

Sometimes you need to clear the request body rather than replace it. If you set the cloned request body to undefined, Angular assumes you intend to leave the body as is. That is not what you want. If you set the cloned request body to null, Angular knows you intend to clear the request body.

The sample app has an AuthService that produces an authorization token. Here is its AuthInterceptor that injects that service to get the token and adds an authorization header with that token to every outgoing request:

The RxJS tap operator captures whether the request succeed or failed. The RxJS finalize operator is called when the response observable either errors or completes (which it must), and reports the outcome to the MessageService.

在这个可观察对象的流中，无论是 tap 还是 finalize 接触过的值，都会照常发送给调用者。

Neither tap nor finalize touch the values of the observable stream returned to the caller.

A revised version of the CachingInterceptor optionally returns an observable that immediately emits the cached response, sends the request to the NPM web API anyway, and emits again later with the updated search results.

A checkbox on the PackageSearchComponent toggles a withRefresh flag, which is one of the arguments to PackageSearchService.search(). That search() method creates the custom x-refresh header and adds it to the request before calling HttpClient.get().

The revised CachingInterceptor sets up a server request whether there's a cached value or not, using the same sendRequest() method described above. The results$ observable will make the request when subscribed.

If there is a cached value, the code pipes the cached response onto results$, producing a recomposed observable that emits twice, the cached response first (and immediately), followed later by the response from the server. Subscribers see a sequence of two responses.

Sometimes applications transfer large amounts of data and those transfers can take a long time. File uploads are a typical example. Give the users a better experience by providing feedback on the progress of such transfers.

The sample app for this guide doesn't have a server that accepts uploaded files. The UploadInterceptor in app/http-interceptors/upload-interceptor.ts intercepts and short-circuits upload requests by returning an observable of simulated events.

Cross-Site Request Forgery (XSRF) is an attack technique by which the attacker can trick an authenticated user into unknowingly executing actions on your website. HttpClient supports a common mechanism used to prevent XSRF attacks. When performing HTTP requests, an interceptor reads a token from a cookie, by default XSRF-TOKEN, and sets it as an HTTP header, X-XSRF-TOKEN. Since only code that runs on your domain could read the cookie, the backend can be certain that the HTTP request came from your client application and not an attacker.

To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called XSRF-TOKEN on either the page load or the first GET request. On subsequent requests the server can verify that the cookie matches the X-XSRF-TOKEN HTTP header, and therefore be sure that only code running on your domain could have sent the request. The token must be unique for each user and must be verifiable by the server; this prevents the client from making up its own tokens. Set the token to a digest of your site's authentication cookie with a salt for added security.

为了防止多个 Angular 应用共享同一个域名或子域时出现冲突，要给每个应用分配一个唯一的 cookie 名称。

In order to prevent collisions in environments where multiple Angular apps share the same domain or subdomain, give each application a unique cookie name.

Note that HttpClient supports only the client half of the XSRF protection scheme. Your backend service must be configured to set the cookie for your page, and to verify that the header is present on all eligible requests. If not, Angular's default protection will be ineffective.

Like any external dependency, the HTTP backend needs to be mocked so your tests can simulate interaction with a remote server. The @angular/common/http/testing library makes setting up such mocking straightforward.

it('can test HttpClient.get',()=>{const testData:Data={name:'Test Data'};// Make an HTTP GET request
httpClient.get<Data>(testUrl).subscribe(data =>// When observable resolves, result should match test data
expect(data).toEqual(testData));// The following `expectOne()` will match the request's URL.// If no requests or multiple requests matched that URL// `expectOne()` would throw.const req = httpTestingController.expectOne('/data');// Assert that the request is a GET.
expect(req.request.method).toEqual('GET');// Respond with mock data, causing Observable to resolve.// Subscribe callback asserts that correct data was returned.
req.flush(testData);// Finally, assert that there are no outstanding requests.
httpTestingController.verify();});

最后一步，验证没有发起过预期之外的请求，足够通用，因此你可以把它移到 afterEach() 中：

The last step, verifying that no requests remain outstanding, is common enough for you to move it into an afterEach() step:

afterEach(() => { // After every test, assert that there are no more pending requests. httpTestingController.verify(); });

afterEach(()=>{// After every test, assert that there are no more pending requests.
httpTestingController.verify();});

If you need to respond to duplicate requests in your test, use the match() API instead of expectOne(). It takes the same arguments but returns an array of matching requests. Once returned, these requests are removed from future matching and you are responsible for flushing and verifying them.

// get all pending requests that match the given URL const requests = httpTestingController.match(testUrl); expect(requests.length).toEqual(3); // Respond to each request with different results requests[0].flush([]); requests[1].flush([testData[0]]); requests[2].flush(testData);

// get all pending requests that match the given URLconst requests = httpTestingController.match(testUrl);
expect(requests.length).toEqual(3);// Respond to each request with different results
requests[0].flush([]);
requests[1].flush([testData[0]]);
requests[2].flush(testData);