사용자 도구

사이트 도구


angular:httpclient

HttpClient

관련 POST

대부분의 프런트 엔드 응용 프로그램은 HTTP 프로토콜을 통해 백엔드 서비스와 통신합니다. 최신 브라우저는 HTTP 요청을하기 위해 XMLHttpRequest 인터페이스와 fetch() API의 두 가지 API를 지원합니다.

@angular/common/httpHttpClient는 브라우저에 의해 노출 된 XMLHttpRequest interface에 의존하는 Angular Application을 위한 단순화 된 클라이언트 HTTP API를 제공합니다. HttpClient의 추가 이점으로는 테스트 가능성 기능, 형식화 된 요청 및 응답 객체, 요청 및 응답 차단, 관찰 가능한 API 및 간소화 된 오류 처리가 있습니다.

Setup

HttpClient를 사용하려면 먼저 Angular HttpClientModule을 가져와야합니다. 대부분의 앱은 루트 AppModule에서 그렇게합니다.

HttpClient는 HTTP를 통해 원격서버와 소통을하는 Angular의 메커니즘이다.

앱의 모든 곳에서 HttpClient를 사용 가능하게하려면:

  • open the root AppModule
  • import the HttpClientModule symbol from @angular/common/http
    import { HttpClientModule }    from '@angular/common/http';
  • add it to the @NgModule.imports array

HttpClientModule을 AppModule로 가져온 다음 HttpClient를 다음 ConfigService 예제와 같이 응용 프로그램 클래스에 삽입 할 수 있습니다.

app/config/config.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
 
@Injectable()
export class ConfigService {
  constructor(private http: HttpClient) { }
}

JSON data 얻기

subscribe를 통해서 request를 요구한다.

this.http.get(this.configUrl)
.subscribe((data: Config) => this.config = {
        heroesUrl: data['heroesUrl'],
        textfile:  data['textfile']
    });

service method는 Observable의 구성 데이터를 반환하기 때문에 component는 method의 return value을 구독합니다. subscribe callback은 데이터 필드를 구성 요소의 구성 객체로 복사합니다. 구성 객체는 표시를 위해 구성 요소 템플리트에서 데이터 바인딩됩니다.

왜 service를 쓰는가

이 예제는 너무 간단해서 Http.get()을 컴포넌트 자체에 작성하고 서비스를 건너 뛰기를 원합니다.

그러나 데이터 액세스는 거의 불가능합니다. 일반적으로 데이터를 사후 처리하고, 오류 처리를 추가하며, 간헐적 인 연결에 대처하기 위해 일부 재시도 논리를 사용합니다.

이 구성 요소는 데이터 액세스 세부 사항으로 인해 복잡해집니다. 이 구성 요소는 이해하기 어렵고 테스트하기가 어려워지며 데이터 액세스 논리를 재사용하거나 표준화 할 수 없습니다.

그렇기 때문에 데이터 액세스를 별도의 서비스에 캡슐화하고 해당 서비스의 구성 요소에서 위임과 같은 단순한 경우에도 데이터 액세스를 데이터 액세스에서 분리하는 것이 가장 좋은 방법입니다.

Type-checking the response

위의 subscribe 콜백은 데이터 값을 추출하기 위해 대괄호 표기법(bracket notation)을 필요로합니다.

.subscribe((data: Config) => this.config = {
    heroesUrl: data['heroesUrl'],
    textfile:  data['textfile']
});

TypeScript가 서비스의 데이터 객체에 heroesUrl 속성이 없다는 오류 메시지가 올바르게 표시되기 때문에 data.heroesUrl을 쓸 수 없습니다.

HttpClient.get () 메소드는 JSON 서버 응답을 익명 객체 유형으로 구문 분석했습니다. 그 물체의 모양이 무엇인지 알지 못합니다.

HttpClient에 응답 형식을 알리면 출력을 더 쉽고 분명하게 사용할 수 있습니다.

먼저, 올바른 모양의 인터페이스를 정의:

export interface Config {
  heroesUrl: string;
  textfile: string;
}

그런 다음 해당 인터페이스를 서비스에서 HttpClient.get() 호출의 유형 매개 변수로 지정:

getConfig() {
  // now returns an Observable of Config
  return this.http.get<Config>(this.configUrl);
}

업데이트 된 구성 요소 메소드의 콜백은 형식화 된 데이터 객체를 수신하므로 더 쉽고 안전합니다:

config: Config;
 
showConfig() {
  this.configService.getConfig()
    // clone the data object, using its known Config shape
    .subscribe((data: Config) => this.config = { ...data });
}

Reading the full response

응답 본문은 필요한 모든 데이터를 반환하지 않습니다. 경우에 따라 서버는 특수 헤더 또는 상태 코드를 반환하여 응용 프로그램 워크 플로에서 중요한 특정 조건을 나타냅니다.

observe option으로 전체 응답을 원한다고 HttpClient에게 알려줍니다.

getConfigResponse(): Observable<HttpResponse<Config>> {
  return this.http.get<Config>(
    this.configUrl, { observe: 'response' });
}

이제 HttpClient.get()은 JSON 데이터가 아닌 입력 된 HttpResponse의 Observable을 반환합니다.

구성 요소의 showConfigResponse() 메서드는 응답 헤더와 구성을 표시합니다.

showConfigResponse() {
  this.configService.getConfigResponse()
    // resp is of type `HttpResponse<Config>`
    .subscribe(resp => {
      // display its headers
      const keys = resp.headers.keys();
      this.headers = keys.map(key =>
        `${key}: ${resp.headers.get(key)}`);
 
      // access the body directly, which is typed as `Config`.
      this.config = { ... resp.body };
    });
}

보는 것과같이, reponse object는 정확한 type의 body속성을 가진다.

Error Handling

서버에서 요청이 실패하거나 네트워크 연결 상태가 좋지 않아 서버에 도달하지 못하면 어떻게됩니까? HttpClient는 성공적인 응답 대신 오류 개체를 반환합니다.

.subscribe()에 두 번째 콜백을 추가하여 구성 요소를 처리 할 수 있습니다.

showConfig() {
  this.configService.getConfig()
    .subscribe(
      (data: Config) => this.config = { ...data }, // success path
      error => this.error = error // error path
    );
}

데이터 액세스가 실패 할 경우 사용자에게 어떤 종류의 피드백을 제공하는 것이 좋습니다. 그러나 HttpClient가 반환 한 원시 오류 객체를 표시하는 것은 최선의 방법과 거리가 멀습니다.

Getting error details

오류가 발생했음을 감지하는 것이 한 가지입니다. 그 오류를 해석하고 사용자에게 친숙한 응답을 작성하는 것은 좀 더 복잡합니다.

두 가지 유형의 오류가 발생할 수 있습니다. 서버 백엔드는 요청을 거부하여 404 또는 500과 같은 상태 코드와 함께 HTTP 응답을 리턴합니다. 이는 오류 응답입니다.

또는 요청이 성공적으로 완료되지 못하게하는 네트워크 오류 또는 RxJS 운영자에게 던져지는 예외와 같은 클라이언트 측에서 문제가 발생할 수 있습니다. 이러한 오류는 JavaScript ErrorEvent 객체를 생성합니다.

HttpClient는 HttpErrorResponse에서 두 가지 종류의 오류를 모두 캡처하고 실제로 발생한 상황을 파악하기 위해 해당 응답을 검사 할 수 있습니다.

오류 검사, 해석 및 해결은 서비스가 아닌 구성 요소에서 수행하려는 작업입니다.

먼저 다음과 같은 오류 처리기를 고안해야합니다.

private handleError(error: HttpErrorResponse) {
  if (error.error instanceof ErrorEvent) {
    // A client-side or network error occurred. Handle it accordingly.
    console.error('An error occurred:', error.error.message);
  } else {
    // The backend returned an unsuccessful response code.
    // The response body may contain clues as to what went wrong,
    console.error(
      `Backend returned code ${error.status}, ` +
      `body was: ${error.error}`);
  }
  // return an observable with a user-facing error message
  return throwError(
    'Something bad happened; please try again later.');
};

이 핸들러는 사용자 친화적 인 오류 메시지와 함께 RxJS Error-Observable을 반환합니다. 서비스 사용자는 서비스 메소드가 Observable을 어떤 종류의 “나쁜”것으로 돌려 주길 기대합니다.

이제 HttpClient 메서드에서 반환 한 Observables를 가져 와서 오류 처리기로 전달합니다.

getConfig() {
  return this.http.get<Config>(this.configUrl)
    .pipe(
      catchError(this.handleError)
    );
}

retry()

때때로 오류는 일시적이며 다시 시도하면 자동으로 사라집니다. 예를 들어 네트워크 중단은 모바일 시나리오에서 흔히 발생하며 다시 시도하면 성공적인 결과를 얻을 수 있습니다.

RxJS 라이브러리는 탐구할만한 몇 가지 재시도 연산자를 제공합니다. 가장 간단한 방법은 retry ()이며 실패한 Observable을 지정된 횟수만큼 자동으로 다시 구독합니다. HttpClient 메서드 호출의 결과에 다시 등록하면 HTTP 요청을 다시 발행하는 효과가 있습니다.

오류 처리기 바로 전에 HttpClient 메서드 결과로 파이프를 연결하십시오.

getConfig() {
  return this.http.get<Config>(this.configUrl)
    .pipe(
      retry(3), // retry a failed request up to 3 times
      catchError(this.handleError) // then handle the error
    );
}

Observables and operators

이 가이드의 이전 섹션에서는 catchError 및 retry와 같은 RxJS Observables 및 연산자에 대해 설명했습니다. 아래에서 계속 진행하면 RxJS 아티팩트가 더 많이 발생합니다.

RxJS는 비동기 및 콜백 기반 코드를 기능적, 반응 적 스타일로 작성하기위한 라이브러리입니다. HttpClient를 포함한 많은 Angular API가 RxJS Observables를 생성하고 사용합니다.

RxJS 자체는이 가이드의 범위를 벗어납니다. 웹에서 많은 학습 자료를 찾을 수 있습니다. 최소한의 RxJS 지식 만 있으면 HttpClient를 효과적으로 사용하기 위해 시간이 지남에 따라 RxJS 기술을 키울 수 있습니다.

이러한 코드 스니펫을 따르고 있다면 해당 스니펫에 나타나는 RxJS 관측 가능 및 연산자 기호를 가져와야합니다. 이러한 ConfigService 가져 오기는 일반적입니다.

import { Observable, throwError } from 'rxjs';
import { catchError, retry } from 'rxjs/operators';

Requesting non-JSON data

모든 API가 JSON 데이터를 반환하지는 않습니다. 이 다음 예제에서 DownloaderService 메서드는 서버에서 텍스트 파일을 읽고 파일 내용을 기록한 다음 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().
  return this.http.get(filename, {responseType: 'text'})
    .pipe(
      tap( // Log the result or error
        data => this.log(filename, data),
        error => this.logError(filename, error)
      )
    );
}

HttpClient.get()은 responseType 옵션 때문에 기본 JSON 대신 문자열을 반환합니다.

RxJS 탭 연산자 ( “도청기”에서와 같이)는 코드가 방해받지 않고 관찰 가능을 통과하면서 오류 및 오류 값을 검사 할 수있게합니다.

DownloaderComponent의 download() 메소드는 서비스 메소드에 등록하여 요청을 시작합니다.

download() {
  this.downloaderService.getTextFile('assets/textfile.txt')
    .subscribe(results => this.contents = results);
}

Sending data to the server

HttpClient는 서버에서 데이터를 가져 오는 것 외에도 변경 요청, 즉 PUT, POST 및 DELETE와 같은 다른 HTTP 메서드로 서버에 데이터를 보내는 요청을 지원합니다.

이 가이드의 샘플 앱에는 히어로를 가져와 사용자가 추가, 삭제 및 업데이트 할 수있게 해주는 “Tour of Heroes”예제의 단순화 된 버전이 포함되어 있습니다.

다음 섹션에서는 샘플 HeroesService의 메소드를 발췌합니다.

Adding headers

많은 서버는 저장 조작을 위해 여분의 헤더가 필요합니다. 예를 들어 요청 본문의 MIME 형식을 명시 적으로 선언하기 위해 “Content-Type”헤더가 필요할 수 있습니다. 또는 서버에 인증 토큰이 필요합니다.

HeroesService는 모든 HttpClient 저장 메소드에 전달되는 httpOptions 객체에서 이러한 헤더를 정의합니다.

import { HttpHeaders } from '@angular/common/http';
 
const httpOptions = {
  headers: new HttpHeaders({
    'Content-Type':  'application/json',
    'Authorization': 'my-auth-token'
  })
};

Making a POST request

애플리케이션은 종종 서버에 데이터를 POST합니다. 양식을 제출할 때 POST됩니다. 다음 예제에서 HeroesService는 영웅을 데이터베이스에 추가 할 때 게시합니다.

/** POST: add a new hero to the database */
addHero (hero: Hero): Observable<Hero> {
  return this.http.post<Hero>(this.heroesUrl, hero, httpOptions)
    .pipe(
      catchError(this.handleError('addHero', hero))
    );
}

HttpClient.post() 메서드는 서버가 새로운 영웅을 반환 할 것으로 기대하는 형식 매개 변수가 있고 리소스 URL을 사용한다는 점에서 get()과 비슷합니다.

두 개의 매개 변수가 더 필요합니다.

  1. hero - 요청 본문에 POST 할 데이터입니다.
  2. httpOptions -이 경우 필요한 헤더를 지정하는 메소드 옵션.

물론 그것은 위에서 설명한 것과 거의 같은 방식으로 오류를 포착합니다.

HeroesComponent는이 서비스 메소드에 의해 반환 된 Observable에 가입하여 실제 POST 작업을 시작합니다.

this.heroesService.addHero(newHero)
  .subscribe(hero => this.heroes.push(hero));

서버가 새로 추가 된 영웅으로 성공적으로 응답하면 구성 요소는 영웅을 표시된 영웅 목록에 추가합니다.

Making a DELETE request

이 애플리케이션은 요청 URL에 영웅의 ID를 전달하여 HttpClient.delete 메소드로 영웅을 삭제합니다.

/** DELETE: delete the hero from the server */
deleteHero (id: number): Observable<{}> {
  const url = `${this.heroesUrl}/${id}`; // DELETE api/heroes/42
  return this.http.delete(url, httpOptions)
    .pipe(
      catchError(this.handleError('deleteHero'))
    );
}

HeroesComponent는 이 서비스 메소드에 의해 리턴 된 Observable에 등록함으로써 실제 DELETE 조작을 초기화한다.

this.heroesService.deleteHero(hero.id).subscribe();

구성 요소가 삭제 작업의 결과를 기대하지 않으므로 콜백없이 구독합니다. 결과를 사용하지 않더라도 구독해야합니다. subscribe() 메소드를 호출하면 관찰 가능 (observable)이 실행되며, 이것은 관찰 요청을 시작하는 것이다.

subscribe()를 호출해야합니다. 그렇지 않으면 아무 일도 일어나지 않습니다. HeroesService.deleteHero()를 호출하면 DELETE 요청이 시작되지 않습니다.
// oops ... subscribe() is missing so nothing happens
this.heroesService.deleteHero(hero.id);

항상 구독하십시오!

HttpClient 메서드는 해당 메서드에서 반환 한 관찰 가능 항목에 대해 subscribe ()를 호출 할 때까지 HTTP 요청을 시작하지 않습니다. 이는 모든 HttpClient 메소드에 적용됩니다.

AsyncPipe는 자동으로 subscribe[구독] (및 unsubscribe[구독 취소])합니다.

HttpClient 메서드에서 반환 된 모든 관찰 가능 항목은 의도적으로 차갑습니다. HTTP 요청의 실행이 지연되므로 실제로 발생하기 전에 관찰 및 관찰 작업을 tap 및 catchError와 같은 추가 작업으로 확장 할 수 있습니다.

subscribe (…)를 호출하면 관찰 가능 객체가 실행되고 HttpClient가 HTTP 요청을 작성하고 서버로 전송합니다.

이러한 관찰 내용은 실제 HTTP 요청에 대한 청사진이라고 생각할 수 있습니다.

사실, 각 subscribe()는 독립적으로 observable을 실행합니다. 두 번 구독하면 두 개의 HTTP 요청이 발생합니다.
const req = http.get<Heroes>('/api/heroes');
// 0 requests made - .subscribe() not called.
req.subscribe();
// 1 request made.
req.subscribe();
// 2 requests made.

Making a PUT request

앱이 리소스를 업데이트 된 데이터로 완전히 대체하기 위해 PUT 요청을 보냅니다. 다음 HeroesService 예제는 POST 예제와 같습니다.

/** PUT: update the hero on the server. Returns the updated hero upon success. */
updateHero (hero: Hero): Observable<Hero> {
  return this.http.put<Hero>(this.heroesUrl, hero, httpOptions)
    .pipe(
      catchError(this.handleError('updateHero', hero))
    );
}

위에서 설명한 이유로 호출자 (이 경우 HeroesComponent.update())는 요청을 시작하기 위해 HttpClient.put()에서 반환 된 관찰 가능 객체에 subscribe()해야합니다.

Advanced usage

우리는 @angular/common/http에서 기본적인 HTTP기능에 대해 논의했지만 때로는 단순 요청을하고 데이터를 다시 가져 오는 것 이상을해야 할 때도 있습니다.

Configuring the request

보내는 요청의 다른 측면은 HttpClient 메서드의 마지막 인수로 전달 된 옵션 개체를 통해 구성 할 수 있습니다.

이전에 HeroesService는 options 객체 (httpOptions)를 save 메소드에 전달하여 기본 헤더를 설정했다. 더 많은 일을 할 수 있습니다.

Update headers

HttpHeaders 클래스의 인스턴스는 변경 불가능하기 때문에 이전 옵션 객체에서 기존 헤더를 직접 수정할 수 없습니다.

대신 set() 메서드를 사용하십시오. 새 변경 내용이 적용된 현재 인스턴스의 복제본을 반환합니다.

다음 요청을하기 전에 (오래된 토큰이 만료 된 후) 인증 헤더를 업데이트하는 방법은 다음과 같습니다.

httpOptions.headers =
  httpOptions.headers.set('Authorization', 'my-new-auth-token');

URL Parameters

URL 검색 매개 변수를 추가하는 것도 비슷한 방식으로 작동합니다. 다음은 검색 단어가 포함 된 영웅을 검색하는 searchHeroes 메소드입니다.

/* GET heroes whose name contains search term */
searchHeroes(term: string): Observable<Hero[]> {
  term = term.trim();
 
  // Add safe, URL encoded search parameter if there is a search term
  const options = term ?
   { params: new HttpParams().set('name', term) } : {};
 
  return this.http.get<Hero[]>(this.heroesUrl, options)
    .pipe(
      catchError(this.handleError<Hero[]>('searchHeroes', []))
    );
}

검색어가있는 경우 코드는 HTML URL 인코딩 검색 매개 변수가있는 옵션 개체를 구성합니다. 용어가 “foo”이면 GET 요청 URL은 api/heroes/?name=foo가됩니다.

HttpParams는 변경할 수 없으므로 set() 메서드를 사용하여 옵션을 업데이트해야합니다.

Debouncing requests

이 샘플에는 npm 패키지 검색 기능이 포함되어 있습니다.

사용자가 검색 상자에 이름을 입력하면 PackageSearchComponent는 해당 이름의 패키지에 대한 검색 요청을 NPM 웹 API에 전송합니다.

다음은 템플릿에서 발췌 한 내용입니다.

<input (keyup)="search($event.target.value)" id="name" placeholder="Search"/>
 
<ul>
  <li *ngFor="let package of packages$ | async">
    <b>{{package.name}} v.{{package.version}}</b> -
    <i>{{package.description}}</i>
  </li>
</ul>

(keyup) 이벤트 바인딩은 모든 키 입력을 구성 요소의 search() 메서드로 보냅니다.

모든 키 입력에 대한 요청을 보내는 것은 많은 비용이 듭니다. 사용자가 입력을 중지하고 요청을 보낼 때까지 기다리는 것이 좋습니다. 이 발췌 부분에서 볼 수 있듯이 RxJS 연산자로 구현하기 쉽습니다.

withRefresh = false;
packages$: Observable<NpmPackageInfo[]>;
private searchText$ = new Subject<string>();
 
search(packageName: string) {
  this.searchText$.next(packageName);
}
 
ngOnInit() {
  this.packages$ = this.searchText$.pipe(
    debounceTime(500),
    distinctUntilChanged(),
    switchMap(packageName =>
      this.searchService.search(packageName, this.withRefresh))
  );
}
 
constructor(private searchService: PackageSearchService) { }

searchText$는 사용자가 제공하는 검색 창 값의 순서입니다. 이것은 RxJS Subject로 정의됩니다. 즉, search() 메소드에서와 같이 next(value)를 호출하여 값을 생성 할 수있는 멀티 캐스팅 관측자입니다.

삽입 된 PackageSearchService에 모든 searchText 값을 직접 전달하는 대신 ngOnInit() 파이프의 코드는 세 개의 연산자를 통해 값을 검색합니다.

  1. debounceTime(500) - 사용자가 입력을 중지 할 때까지 기다립니다 (이 경우 1/2 초).
  2. distinctUntilChanged() - 검색 텍스트가 변경 될 때까지 대기합니다.
  3. switchMap() - 서비스에 검색 요청을 보냅니다.

코드는이 재구성 된 검색 결과의 Observable에 packages$를 설정합니다. 이 템플릿은 AsyncPipe를 사용하여 packages$를 구독하고 도착한 검색 결과를 표시합니다.

검색 값은 새 값이고 사용자가 입력을 중지 한 경우에만 서비스에 도달합니다.

withRefresh 옵션은 아래에 설명되어 있습니다.

switchMap()

switchMap () 연산자에는 세 가지 중요한 특징이 있습니다.

  1. Observable을 반환하는 함수 인수를 사용합니다. 다른 데이터 서비스 메소드와 마찬가지로 PackageSearchService.search가 Observable을 반환합니다.
  2. 이전 검색 요청이 아직 연결 상태가 좋지 않은 상태에서 계속 진행중인 경우 해당 요청을 취소하고 새 요청을 보냅니다.
  3. 서버가 순서가 바뀌지 않는 경우에도 원래 요청 순서대로 서비스 응답을 반환합니다.
이 디 바운싱 로직을 재사용 할 생각이라면 유틸리티 함수 또는 PackageSearchService 자체로 옮기는 것을 고려하십시오.

Intercepting requests and responses

HTTP Interception는 @angular/common/http의 주요 기능입니다. interceptor를 사용하면 애플리케이션의 HTTP 요청을 검사하여 서버로 변환하는 interceptor를 선언 할 수 있습니다. 동일한 interceptor는 서버의 응답을 검사하여 응용 프로그램으로 다시 변환 할 수 있습니다. 다중 interceptor는 request/response 처리기의 forward-and-backward 체인을 구성합니다.

interceptor는 모든 HTTP request/response에 대해 일상적인 표준 방법으로 인증에서 로깅에 이르는 다양한 암시 적 작업을 수행 할 수 있습니다.

interceptor가 없으면 개발자는 각 HttpClient 메서드 호출에 대해 이러한 작업을 명시적으로 구현해야합니다.

Write an interceptor

인터셉터를 구현하려면 HttpInterceptor 인터페이스의 intercept() 메소드를 구현하는 클래스를 선언하십시오.

아무 것도하지 않고 그냥 요청을 전달하는 무의미한 요격기 요격기가 있습니다.

import { Injectable } from '@angular/core';
import {
  HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
} from '@angular/common/http';
 
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);
  }
}

intercept 메서드는 request을 Observable로 변환하여 결국 HTTP response을 반환합니다. 이러한 의미에서 각 인터셉터는 완전히 그 자체로 요청을 처리 할 수 있습니다.

대부분의 인터셉터는 HttpHandler 인터페이스를 구현하는 다음 객체의 handle() 메소드에 대한 (아마도 변경된) request을 전달하고 전달하는 response을 검사합니다.

export abstract class HttpHandler {
  abstract handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;
}

intercept()처럼 handle() 메서드는 HTTP 요청을 서버의 응답을 포함하는 HttpEvents Observable로 변환합니다. intercept() 메소드는 관찰자를 검사하여 호출자에게 반환하기 전에이를 변경할 수 있습니다.

이 no-op 인터셉터는 단순히 원래 요청으로 next.handle()을 호출하고 일을하지 않고 observable을 반환합니다.

The next object

다음 객체는 인터셉터 체인의 다음 인터셉터를 나타냅니다. 마지막으로 체인에있는 요청은 서버에 요청을 보내고 서버의 응답을받는 HttpClient 백엔드 처리기입니다.

대부분의 인터셉터는 next.handle ()을 호출하여 요청이 다음 인터셉터와 결국 백엔드 처리기로 전달되도록합니다. 인터셉터는 next.handle () 호출을 건너 뛰고, 체인을 단락시키고, 자체 서버 Observable을 인공 서버 응답으로 리턴 할 수 있습니다.

이것은 Express.js와 같은 프레임 워크에서 흔히 볼 수있는 미들웨어 패턴입니다.

Provide the interceptor

NoopInterceptor는 Angular의 DI (Dependency Injection) 시스템으로 관리되는 서비스입니다. 다른 서비스와 마찬가지로, 응용 프로그램이 사용하기 전에 인터셉터 클래스를 제공해야합니다.

인터셉터는 HttpClient 서비스의 종속성 (선택 사항)이므로 HttpClient를 제공하는 동일한 인젝터 (또는 인젝터의 부모)에 제공해야합니다. DI가 HttpClient를 생성 한 후에 제공되는 인터셉터는 무시됩니다.

이 앱은 AppModule에서 HttpClientModule을 가져 오는 부작용으로 앱의 루트 인젝터에 HttpClient를 제공합니다. AppModule에서도 인터셉터를 제공해야합니다.

@angular/common/http에서 HTTP_INTERCEPTORS 주입 토큰을 가져온 후 다음과 같이 NoopInterceptor 공급자를 작성합니다.

{ provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },

multi : true 옵션을 주목하십시오. 이 필수 설정은 Angle에 HTTP_INTERCEPTORS가 단일 값이 아닌 값 배열을 주입하는 다중 제공자에 대한 토큰임을 나타냅니다.

이 제공자를 AppModule의 공급자 배열에 직접 추가 할 수 있습니다. 그러나 다소 장황하고 더 많은 인터셉터를 만들어 동일한 방식으로 제공 할 수있는 좋은 기회가 있습니다. 또한 이러한 인터셉터를 제공하는 순서에 세심한주의를 기울여야합니다.

모든 인터셉터 제공자를 httpInterceptorProviders 배열로 모으는 “배럴 (barrel)“파일을 작성하는 것을 고려하십시오.이 첫 번째 노드 인 NoopInterceptor부터 시작하십시오.

/* "Barrel" of Http Interceptors */
import { HTTP_INTERCEPTORS } from '@angular/common/http';
 
import { NoopInterceptor } from './noop-interceptor';
 
/** Http interceptor providers in outside-in order */
export const httpInterceptorProviders = [
  { provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },
];

그런 다음 가져 와서 다음과 같이 AppModule 공급자 배열에 추가합니다.

providers: [
  httpInterceptorProviders
],

새로운 인터셉터를 만들 때 httpInterceptorProviders 배열에 추가하면 AppModule을 다시 방문 할 필요가 없습니다.

전체 샘플 코드에는 더 많은 인터셉터가 있습니다.

Interceptor order

Angular는 사용자가 제공 한 순서대로 인터셉터를 적용합니다. 인터셉터 A, B, C를 제공하면 요청은 A→ B→ C로 흐르고 응답은 C→ B→ A로 흐르게됩니다.

주문을 변경하거나 나중에 인터셉터를 제거 할 수 없습니다. 인터셉터를 동적으로 활성화 및 비활성화해야하는 경우 인터셉터 자체에 해당 기능을 구현해야합니다.

HttpEvents

대부분의 HttpClient 메소드가 수행하는 것처럼 intercept() 및 handle() 메서드가 HttpResponse<any>의 관찰 가능 항목을 반환 할 것으로 예상했을 수 있습니다.

대신 HttpEvent<any>의 관찰 가능 항목을 반환합니다.

인터셉터는 HttpClient 메소드보다 낮은 수준에서 작동하기 때문입니다. 단일 HTTP 요청은 업로드 및 다운로드 진행 이벤트를 포함하여 여러 이벤트를 생성 할 수 있습니다. HttpResponse 클래스 자체는 실제로 이벤트 유형이며 HttpEventType.HttpResponseEvent입니다.

많은 인터셉터는 나가는 요청에만 관심이 있으며 next.handle()에서 이벤트 스트림을 수정하지 않고 반환합니다.

그러나 next.handle()의 응답을 검사하고 수정하는 인터셉터는 이러한 모든 이벤트를 볼 수 있습니다. 인터셉터는 그렇지 않은 다른 이유가없는 한 손대지 않은 모든 이벤트를 반환해야합니다.

Immutability

인터셉터는 요청과 응답을 변경시킬 수 있지만 HttpRequest 및 HttpResponse 인스턴스 속성은 읽기 전용이므로 거의 변경되지 않습니다.

좋은 이유는 불변합니다. 앱이 성공하기 전에 요청을 여러 번 다시 시도 할 수 있습니다. 즉, 인터셉터 체인이 동일한 요청을 여러 번 다시 처리 할 수 있습니다. 인터셉터가 원래 요청 객체를 수정할 수있는 경우 재 시도 된 작업은 원본이 아닌 수정 된 요청에서 시작됩니다. Immutability는 인터셉터가 각 시도에 대해 동일한 요청을 볼 수 있도록합니다.

TypeScript는 HttpRequest 읽기 전용 속성을 설정하지 못하게합니다.

// Typescript disallows the following assignment because req.url is readonly
req.url = req.url.replace('http://', 'https://');

요청을 변경하려면 먼저 복제하고 next.handle()에 전달하기 전에 복제본을 수정하십시오. 이 예와 같이 요청을 단일 단계에서 복제하고 수정할 수 있습니다.

// 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);

clone() 메서드의 해시 인수를 사용하면 요청의 특정 속성을 변경하면서 다른 속성을 복사 할 수 있습니다.

The request body

readonly 할당 보호는 딥 업데이트를 방지 할 수 없으며 특히 요청 본문 개체의 속성을 수정하지 못하게 할 수 없습니다.

req.body.name = req.body.name.trim(); // bad idea!

request body을 변경해야하는, 경우 먼저 복사 한 다음 복사본을 변경하고 요청을 clone() 한 다음 다음 예제와 같이 복제 본문을 새 본문으로 설정합니다.

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

Clearing the request body

경우에 따라 요청 본문을 대체하지 않고 삭제해야하는 경우도 있습니다. 복제 된 요청 본문을 undefined로 설정하면 Angular는 본문을 그대로 두려고한다고 가정합니다. 그것은 당신이 원하는 것이 아닙니다. 복제 된 요청 본문을 null로 설정하면 Angular는 요청 본문을 지우려는 의도가 있음을 알고 있습니다.

newReq = req.clone({ ... }); // body not mentioned => preserve original body
newReq = req.clone({ body: undefined }); // preserve original body
newReq = req.clone({ body: null }); // clear the body

Set default headers

앱은 종종 인터셉터를 사용하여 발신 요청에 기본 헤더를 설정합니다.

샘플 앱에는 인증 토큰을 생성하는 AuthService가 있습니다. 다음은 AuthInceptceptor가 해당 서비스를 주입하여 토큰을 가져오고 해당 토큰과 함께 승인 헤더를 모든 발신 요청에 추가합니다.

import { AuthService } from '../auth.service';
 
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
 
  constructor(private auth: AuthService) {}
 
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    // Get the auth token from the service.
    const authToken = this.auth.getAuthorizationToken();
 
    // Clone the request and replace the original headers with
    // cloned headers, updated with the authorization.
    const authReq = req.clone({
      headers: req.headers.set('Authorization', authToken)
    });
 
    // send cloned request with header to the next handler.
    return next.handle(authReq);
  }
}

새 헤더를 설정하라는 요청을 복제하는 관행은 너무 일반적이어서 setHeaders 단축키가 있습니다.

// Clone the request and set the new header in one step.
const authReq = req.clone({ setHeaders: { Authorization: authToken } });

헤더를 변경하는 인터셉터는 다음과 같은 여러 가지 다른 작업에 사용할 수 있습니다.

  1. Authentication/authorization (인증/승인)
  2. Caching behavior(캐싱동작); 예 : If-Modified-Since
  3. XSRF 보호

Logging

인터셉터는 요청과 응답을 함께 처리 할 수 있기 때문에 시간과 같은 작업을 수행하고 전체 HTTP 작업을 기록 할 수 있습니다.

다음의 LoggingInterceptor를 고려해보십시오.이 LoggingInterceptor는 요청 시간과 응답 시간을 캡처하고 MessageService가 삽입 된 경과 시간으로 결과를 기록합니다.

import { finalize, tap } from 'rxjs/operators';
import { MessageService } from '../message.service';
 
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
  constructor(private messenger: MessageService) {}
 
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const started = Date.now();
    let ok: string;
 
    // extend server response observable with logging
    return next.handle(req)
      .pipe(
        tap(
          // Succeeds when there is a response; ignore other events
          event => ok = event instanceof HttpResponse ? 'succeeded' : '',
          // Operation failed; error is an HttpErrorResponse
          error => ok = 'failed'
        ),
        // Log when response observable either completes or errors
        finalize(() => {
          const elapsed = Date.now() - started;
          const msg = `${req.method} "${req.urlWithParams}"
             ${ok} in ${elapsed} ms.`;
          this.messenger.add(msg);
        })
      );
  }
}

RxJS 탭 운영자는 요청의 성공 또는 실패 여부를 캡처합니다. RxJS finalize 연산자는 응답이 관찰되거나 완료 될 때 호출되며 MessageService에 결과를보고합니다.

탭하거나 파이널 라이즈하지 않으면 발신자에게 반환 된 관찰 가능한 스트림의 값을 터치하지 않습니다.

Caching

인터셉터는 next.handle()에 전달하지 않고 스스로 요청을 처리 할 수 있습니다.

예를 들어 특정 요청 및 응답을 캐시하여 성능을 향상시킬 수 있습니다. 기존 데이터 서비스를 방해하지 않고 인터셉터에 캐싱을 위임 할 수 있습니다.

CachingInterceptor는 이러한 접근 방식을 보여줍니다.

@Injectable()
export class CachingInterceptor implements HttpInterceptor {
  constructor(private cache: RequestCache) {}
 
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    // continue if not cachable.
    if (!isCachable(req)) { return next.handle(req); }
 
    const cachedResponse = this.cache.get(req);
    return cachedResponse ?
      of(cachedResponse) : sendRequest(req, next, this.cache);
  }
}

isCachable() 함수는 요청이 캐시 가능한지 여부를 결정합니다. 이 샘플에서는 npm 패키지 검색 API에 대한 GET 요청 만 캐시 할 수 있습니다.

요청이 캐시 가능하지 않은 경우 인터셉터는 요청을 체인의 다음 핸들러로 전달하기 만합니다.

캐시 가능한 요청이 캐시에서 발견되면 인터셉터는 캐시 된 응답과 함께 of() observable을 반환하고, 다음 처리기 (및 모든 다른 인터셉터를 다운 스트림)를 by-passing합니다.

캐시 가능한 요청이 캐시에 없으면 코드는 sendRequest를 호출합니다.

/**
 * Get server response observable by sending request to `next()`.
 * Will add the response to the cache on the way out.
 */
function sendRequest(
  req: HttpRequest<any>,
  next: HttpHandler,
  cache: RequestCache): Observable<HttpEvent<any>> {
 
  // No headers allowed in npm search request
  const noHeaderReq = req.clone({ headers: new HttpHeaders() });
 
  return next.handle(noHeaderReq).pipe(
    tap(event => {
      // There may be other events besides the response.
      if (event instanceof HttpResponse) {
        cache.put(req, event); // Update the cache.
      }
    })
  );
}

sendRequest 함수는 npm api가 헤더를 사용하지 않으므로 헤더가없는 요청 복제를 만듭니다.

이 요청은 next.handle()에 전달되어 궁극적으로 서버를 호출하고 서버의 응답을 반환합니다.

sendRequest가 응답을 응용 프로그램으로 가로 채는 것을 확인하십시오. 그것은 call() 함수가 캐시에 응답을 추가하는 tap() 연산자를 통해 응답을 파이프합니다.

원래 응답은 응용 프로그램 호출자에게 인터셉터 체인을 통해 변경되지 않은 상태로 계속 유지됩니다.

PackageSearchService와 같은 데이터 서비스는 HttpClient 요청 중 일부가 실제로 캐시 된 응답을 반환한다는 것을 알지 못합니다.

Return a multi-valued Observable

HttpClient.get () 메서드는 일반적으로 데이터 또는 오류를 내보내는 관찰 가능 항목을 반환합니다. 어떤 사람들은 그것을 “한 번에 끝낸”관찰 가능한 것으로 묘사합니다.

그러나 인터셉터는 이것을 한 번 이상 내보내는 관찰자로 변경할 수 있습니다.

개정 된 버전의 CachingInterceptor는 관찰 된 결과를 캐시 된 응답을 즉시 내보내고 NPM 웹 API에 요청을 보내고 나중에 업데이트 된 검색 결과와 함께 다시 방출합니다.

// cache-then-refresh
if (req.headers.get('x-refresh')) {
  const results$ = sendRequest(req, next, this.cache);
  return cachedResponse ?
    results$.pipe( startWith(cachedResponse) ) :
    results$;
}
// cache-or-fetch
return cachedResponse ?
  of(cachedResponse) : sendRequest(req, next, this.cache);

cache-then-refresh 옵션은 사용자 정의 x-refresh 헤더가 있으면 트리거됩니다.

PackageSearchComponent의 확인란은 PackageSearchService.search ()에 대한 인수 중 하나 인 withRefresh 플래그를 토글합니다. search () 메서드는 사용자 정의 x-refresh 헤더를 만들고 HttpClient.get ()을 호출하기 전에 요청에 추가합니다.

수정 된 CachingInterceptor는 위에서 설명한 것과 같은 sendRequest () 메서드를 사용하여 캐시 된 값의 존재 여부와 상관없이 서버 요청을 설정합니다. 관찰 가능한 result$는 구독 할 때 요청할 것입니다.

캐시 된 값이 없으면 인터셉터는 result$를 반환합니다.

캐시 된 값이 있으면 코드는 캐시 된 응답을 result$로 파이프하여 두 번 내보내는 재구성된 관찰 가능 항목을 생성하고 캐시된 응답을 먼저 (그리고 바로) 응답 한 다음 나중에 서버에서 응답합니다. 구독자는 두개의 response 시퀀스를 봅니다.

Listening to progress events

때때로 응용 프로그램은 많은 양의 데이터를 전송하며 이러한 전송에는 오랜 시간이 걸릴 수 있습니다. 파일 업로드가 전형적인 예입니다. 이러한 전송 진행 상황에 대한 피드백을 제공하여 사용자에게 더 나은 경험을 제공하십시오.

진행 이벤트를 사용하도록 요청하려면 reportProgress 옵션을 true로 설정하여 HttpRequest의 인스턴스를 만들어 진행 이벤트를 추적 할 수 있습니다.

const req = new HttpRequest('POST', '/upload/file', file, {
  reportProgress: true
});
모든 진행 이벤트는 변경 감지를 트리거하므로, UI의 진행 상태를 진정으로보고하려는 경우에만 진행 상태를 설정하십시오.

HTTP 메소드와 함께 HttpClient#request()를 사용할 때 observe: 'events'를 사용하여 구성하면 전송 진행 상황을 비롯한 모든 이벤트를 볼 수 있습니다.

그런 다음이 요청 개체를 HttpClient.request () 메서드에 전달합니다.이 메서드는 인터셉터에서 처리 한 것과 동일한 이벤트 인 Observable of HttpEvents를 반환합니다.

// The `HttpClient.request` API produces a raw event stream
// which includes start (sent), progress, and response events.
return this.http.request(req).pipe(
  map(event => this.getEventMessage(event, file)),
  tap(message => this.showProgress(message)),
  last(), // return last (completed) message to caller
  catchError(this.handleError(file))
);

getEventMessage 메소드는 이벤트 스트림에서 각 유형의 HttpEvent를 해석합니다.

/** Return distinct message for sent, upload progress, & response events */
private getEventMessage(event: HttpEvent<any>, file: File) {
  switch (event.type) {
    case HttpEventType.Sent:
      return `Uploading file "${file.name}" of size ${file.size}.`;
 
    case HttpEventType.UploadProgress:
      // Compute and show the % done:
      const percentDone = Math.round(100 * event.loaded / event.total);
      return `File "${file.name}" is ${percentDone}% uploaded.`;
 
    case HttpEventType.Response:
      return `File "${file.name}" was completely uploaded!`;
 
    default:
      return `File "${file.name}" surprising upload event: ${event.type}.`;
  }
}
이 가이드의 샘플 앱에는 업로드 된 파일을 허용하는 서버가 없습니다. app/http-interceptors/upload-interceptor.ts에있는 UploadInterceptor는 시뮬레이션 된 이벤트의 관찰 결과를 반환하여 업로드 요청을 가로 채고 단락시킵니다.

Security: XSRF Protection

XSRF (Cross-Site Request Forgery)는 공격자가 인증 된 사용자를 속여 웹 사이트에서 무의식적으로 실행하는 공격 기법입니다. HttpClient는 XSRF 공격을 막는 데 사용되는 일반적인 메커니즘을 지원합니다. HTTP 요청을 수행 할 때 인터셉터는 기본적으로 XSRF-TOKEN에 의해 ​​쿠키에서 토큰을 읽고 HTTP 헤더 인 X-XSRF-TOKEN으로 설정합니다. 도메인에서 실행되는 코드 만 쿠키를 읽을 수 있기 때문에 백엔드는 HTTP 요청이 공격자가 아닌 클라이언트 응용 프로그램에서 온 것임을 확신 할 수 있습니다.

기본적으로 인터셉터는 모든 변이 된 요청 (POST 등)에 대해이 쿠키를 상대 URL로 보내지 만 GET / HEAD 요청이나 절대 URL이있는 요청에는 보내지 않습니다.

이 기능을 이용하려면 서버가 페이지로드 또는 첫 번째 GET 요청에서 XSRF-TOKEN이라는 자바 스크립트로 읽을 수있는 세션 쿠키로 토큰을 설정해야합니다. 후속 요청에서 서버는 쿠키가 X-XSRF-TOKEN HTTP 헤더와 일치하는지 확인할 수 있으므로 도메인에서 실행중인 코드 만 요청을 보낼 수 있는지 확인하십시오. 토큰은 각 사용자마다 고유해야하며 서버에서 검증 할 수 있어야합니다. 이것은 클라이언트가 자신의 토큰을 만들지 못하게합니다. 토큰을 보안 강화를 위해 소금으로 사이트의 인증 쿠키 다이제스트로 설정하십시오.

여러 Angular 응용 프로그램이 동일한 도메인 또는 하위 도메인을 공유하는 환경에서 충돌을 방지하려면 각 응용 프로그램에 고유 한 쿠키 이름을 지정하십시오.

HttpClient는 XSRF 보호 체계의 클라이언트 절반 만 지원합니다. 백엔드 서비스는 페이지에 대한 쿠키를 설정하고 모든 적합한 요청에 헤더가 있는지 확인하도록 구성되어야합니다. 그렇지 않은 경우 Angular의 기본 보호가 비효율적입니다.

백엔드 서비스가 XSRF 토큰 쿠키 또는 헤더에 대해 다른 이름을 사용하는 경우 HttpClientXsrfModule.withOptions ()를 사용하여 기본값을 대체하십시오.

imports: [
  HttpClientModule,
  HttpClientXsrfModule.withOptions({
    cookieName: 'My-Xsrf-Cookie',
    headerName: 'My-Xsrf-Header',
  }),
],

Testing HTTP requests

외부 종속성과 마찬가지로 HTTP 백엔드가 조롱 받아 테스트가 원격 서버와의 상호 작용을 시뮬레이션 할 수 있어야합니다. @ angular / common / http / testing 라이브러리는 그러한 조롱을 쉽게 설정합니다.

Mocking philosophy

Angular의 HTTP 테스트 라이브러리는 앱이 코드를 실행하고 요청을 먼저 처리하는 테스트 패턴을 위해 설계되었습니다.

그런 다음 특정 요청이 수행되었거나 수행되지 않았으며 해당 요청에 대한 어설 션을 수행하고 마지막으로 예상되는 요청을 “플러시 (flushing)“하여 응답을 제공합니다.

결국 테스트에서 앱이 예기치 않은 요청을하지 않았 음을 확인할 수 있습니다.

라이브 코딩 환경에서 이러한 샘플 테스트 / 다운로드 예제를 실행할 수 있습니다.

이 가이드에서 설명하는 테스트는 src/testing/http-client.spec.ts에 있습니다. src/app/heroes/heroes.service.spec.ts에서 HttpClient를 호출하는 애플리케이션 데이터 서비스에 대한 테스트도 있습니다.

Setup

HttpClient에 대한 호출을 테스트하려면 테스트에 필요한 다른 기호와 함께 HttpClientTestingModule 및 조롱 컨트롤러 HttpTestingController를 가져옵니다.

// Http testing module and mocking controller
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
 
// Other imports
import { TestBed } from '@angular/core/testing';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';

그런 다음 HttpClientTestingModule을 TestBed에 추가하고 service-under-test의 설정을 계속합니다.

describe('HttpClient testing', () => {
  let httpClient: HttpClient;
  let httpTestingController: HttpTestingController;
 
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [ HttpClientTestingModule ]
    });
 
    // Inject the http service and test controller for each test
    httpClient = TestBed.get(HttpClient);
    httpTestingController = TestBed.get(HttpTestingController);
  });
  /// Tests begin ///
});

이제 테스트 과정에서 요청이 정상 백엔드가 아닌 테스트 백엔드에 부딪칩니다.

또한이 설정은 TestBed.get ()을 호출하여 테스트 중에 참조 될 수 있도록 HttpClient 서비스 및 조롱 컨트롤러를 주입합니다.

Expecting and answering requests

이제 GET 요청이 발생할 것으로 예상되는 테스트를 작성하고 모의 응답을 제공 할 수 있습니다.

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 () 단계로 이동할 수 있습니다.

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

Custom request expectations

URL로 일치하는 것만으로 충분하지 않으면 자체 매칭 기능을 구현할 수 있습니다. 예를 들어, 인증 헤더가있는 발신 요청을 찾을 수 있습니다.

// Expect one request with an authorization header
const req = httpTestingController.expectOne(
  req => req.headers.has('Authorization')
);

이전 expectOne()과 마찬가지로 0 또는 2 개 이상의 요청이이 조건을 충족하면 테스트가 실패합니다.

Handling more than one request

테스트에서 중복 요청에 응답해야하는 경우 expectOne () 대신 match () API를 사용하십시오. 동일한 인수를 사용하지만 일치하는 요청의 배열을 반환합니다. 일단 반환되면 이러한 요청은 나중에 일치하는 것으로부터 제거되며 사용자는이를 플러시하고 확인해야합니다.

// 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);

Testing for errors

실패한 HTTP 요청에 대해 앱의 방어를 테스트해야합니다.

다음 예제와 같이 request.flush ()를 호출하여 오류 메시지를 표시합니다.

it('can test for 404 error', () => {
  const emsg = 'deliberate 404 error';
 
  httpClient.get<Data[]>(testUrl).subscribe(
    data => fail('should have failed with the 404 error'),
    (error: HttpErrorResponse) => {
      expect(error.status).toEqual(404, 'status');
      expect(error.error).toEqual(emsg, 'message');
    }
  );
 
  const req = httpTestingController.expectOne(testUrl);
 
  // Respond with mock error
  req.flush(emsg, { status: 404, statusText: 'Not Found' });
});

또는 ErrorEvent와 함께 request.error()를 호출 할 수 있습니다.

it('can test for network error', () => {
  const emsg = 'simulated network error';
 
  httpClient.get<Data[]>(testUrl).subscribe(
    data => fail('should have failed with the network error'),
    (error: HttpErrorResponse) => {
      expect(error.error.message).toEqual(emsg, 'message');
    }
  );
 
  const req = httpTestingController.expectOne(testUrl);
 
  // Create mock ErrorEvent, raised when something goes wrong at the network level.
  // Connection timeout, DNS error, offline, etc
  const mockError = new ErrorEvent('Network error', {
    message: emsg,
  });
 
  // Respond with mock error
  req.error(mockError);
});
angular/httpclient.txt · 마지막으로 수정됨: 2025/04/15 10:05 저자 127.0.0.1