Перейти к содержанию

Связь с сервером HTTP

📅 27.02.2023

Большинству внешних приложений необходимо взаимодействовать с сервером по протоколу HTTP для загрузки или выгрузки данных и доступа к другим внутренним сервисам.

Настройка для взаимодействия с сервером

Прежде чем использовать HttpClient, необходимо импортировать модуль Angular HttpClientModule. Большинство приложений делают это в корневом AppModule.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
    imports: [
        BrowserModule,
        // import HttpClientModule after BrowserModule.
        HttpClientModule,
    ],
    declarations: [AppComponent],
    bootstrap: [AppComponent],
})
export class AppModule {}

Затем вы можете внедрить сервис HttpClient в качестве зависимости класса приложения, как показано в следующем примере ConfigService.

1
2
3
4
5
6
7
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class ConfigService {
    constructor(private http: HttpClient) {}
}

Сервис HttpClient использует observables для всех транзакций. Вы должны импортировать RxJS observable и символы операторов, которые появляются в фрагментах примера.

Эти импорты ConfigService являются типичными.

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

Вы можете запустить пример, который сопровождает данное руководство.

Пример приложения не требует сервера данных. Он полагается на Angular in-memory-web-api, который заменяет HttpBackend модуля HttpClient.

Заменяющий сервис имитирует поведение REST-подобного бэкенда.

Посмотрите на AppModule imports, чтобы увидеть, как он настроен.

Запрос данных с сервера

Используйте метод HttpClient.get() для получения данных с сервера. Асинхронный метод посылает HTTP-запрос и возвращает Observable, который выдает запрошенные данные при получении ответа.

Тип возвращаемых данных зависит от значений observe и responseType, которые вы передаете в вызов.

Метод get() принимает два аргумента: URL конечной точки, с которой нужно получить данные, и объект options, который используется для настройки запроса.

1
2
3
4
5
6
7
8
options: {
  headers?: HttpHeaders | {[header: string]: string | string[]},
  observe?: 'body' | 'events' | 'response',
  params?: HttpParams|{[param: string]: string | number | boolean | ReadonlyArray<string | number | boolean>},
  reportProgress?: boolean,
  responseType?: 'arraybuffer'|'blob'|'json'|'text',
  withCredentials?: boolean,
}

Важные параметры включают свойства observe и responseType.

  • Параметр observe определяет, какую часть ответа возвращать.
  • Параметр responseType определяет формат, в котором будут возвращаться данные

Используйте объект options для настройки различных других аспектов исходящего запроса. Например, при добавлении заголовков служба устанавливает заголовки по умолчанию с помощью свойства headers опции.

Используйте свойство params для настройки запроса с параметрами URL HTTP, а также опцию reportProgress для прослушивания событий о ходе выполнения при передаче больших объемов данных.

Приложения часто запрашивают JSON-данные с сервера. В примере ConfigService приложению нужен файл конфигурации на сервере, config.json, в котором указаны URL-адреса ресурсов.

1
2
3
4
5
{
    "heroesUrl": "api/heroes",
    "textfile": "assets/textfile.txt",
    "date": "2020-01-29"
}

Чтобы получить такие данные, вызов get() должен содержать следующие параметры: {observe: 'body', responseType: 'json'}. Это значения по умолчанию для этих опций, поэтому в следующих примерах объект options не передается.

В последующих разделах показаны некоторые дополнительные возможности опций.

Пример соответствует лучшим практикам создания масштабируемых решений путем определения повторно используемого injectable service для выполнения функциональности обработки данных. В дополнение к получению данных, сервис может пост-обработать данные, добавить обработку ошибок и логику повторных попыток.

Служба ConfigService получает этот файл с помощью метода HttpClient.get().

1
2
3
4
5
configUrl = 'assets/config.json';

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

Компонент ConfigComponent инжектирует ConfigService и вызывает метод сервиса getConfig.

Поскольку метод сервиса возвращает Observable данных конфигурации, компонент подписывается на возвращаемое значение метода. Обратный вызов подписки выполняет минимальную постобработку.

Он копирует поля данных в объект config компонента, который привязывается к данным в шаблоне компонента для отображения.

1
2
3
4
5
6
7
8
showConfig() {
  this.configService.getConfig()
    .subscribe((data: Config) => this.config = {
        heroesUrl: data.heroesUrl,
        textfile:  data.textfile,
        date: data.date,
    });
}

Начало запроса

Для всех методов HttpClient, метод не начинает свой HTTP запрос, пока вы не вызовете subscribe() на наблюдаемой, которую возвращает метод.

Это верно для всех HttpClient методов.

Вы всегда должны отписываться от наблюдаемого компонента, когда компонент уничтожается.

Все наблюдаемые, возвращаемые из методов HttpClient, по своей конструкции являются холодными. Выполнение HTTP-запроса отложено, что позволяет вам расширить наблюдаемую таблицу дополнительными операциями, такими как tap и catchError, прежде чем что-то произойдет.

Вызов subscribe() запускает выполнение наблюдаемой таблицы и заставляет HttpClient составить и отправить HTTP-запрос на сервер.

Рассматривайте эти наблюдаемые как блюпринты для реальных HTTP-запросов.

Фактически, каждая subscribe() инициирует отдельное, независимое выполнение наблюдаемой. Подписка дважды приводит к двум HTTP-запросам.

1
2
3
4
5
6
const req = http.get<Heroes>('/api/heroes');
// 0 requests made - .subscribe() not called.
req.subscribe();
// 1 request made.
req.subscribe();
// 2 requests made.

Запрос типизированного ответа

Структурируйте ваш запрос HttpClient, чтобы объявить тип объекта ответа, чтобы сделать потребление вывода более простым и очевидным. Указание типа ответа действует как утверждение типа во время компиляции.

Указание типа ответа — это заявление TypeScript о том, что он должен рассматривать ваш ответ как объект данного типа. Это проверка во время сборки и не гарантирует, что сервер действительно ответит объектом данного типа. Сервер должен убедиться, что в ответ будет получен тип, указанный API сервера.

Чтобы задать тип объекта ответа, сначала определите интерфейс с необходимыми свойствами. Используйте интерфейс, а не класс, потому что ответ — это обычный объект, который не может быть автоматически преобразован в экземпляр класса.

1
2
3
4
5
export interface Config {
    heroesUrl: string;
    textfile: string;
    date: any;
}

Затем укажите этот интерфейс в качестве параметра типа вызова HttpClient.get() в сервисе.

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

Когда вы передаете интерфейс в качестве параметра типа в метод HttpClient.get(), используйте оператор RxJS map для преобразования данных ответа в соответствии с требованиями пользовательского интерфейса. Затем вы можете передать преобразованные данные в async pipe.

Обратный вызов в методе обновленного компонента получает типизированный объект данных, который проще и безопаснее потреблять:

1
2
3
4
5
6
7
config: Config | undefined;

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

Чтобы получить доступ к свойствам, определенным в интерфейсе, вы должны явно преобразовать простой объект, полученный из JSON, в требуемый тип ответа. Например, следующий обратный вызов subscribe получает data как Object, а затем преобразует его в тип, чтобы получить доступ к свойствам.

1
2
3
4
.subscribe(data => this.config = {
  heroesUrl: (data as any).heroesUrl,
  textfile:  (data as any).textfile,
});

observe и response типы

Типы опций observe и response являются строковыми союзами, а не обычными строками.

1
2
3
4
5
6
7
options: {

observe?: 'body' | 'events' | 'response',

responseType?: 'arraybuffer'|'blob'|'json'|'text',

}

Это может привести к путанице. Например:

1
2
3
4
5
6
7
8
// this works
client.get('/foo', { responseType: 'text' });

// but this does NOT work
const options = {
    responseType: 'text',
};
client.get('/foo', options);

Во втором случае TypeScript определяет тип options как {responseType: string}. Этот тип слишком широк, чтобы передать его в HttpClient.get, который ожидает, что тип responseType будет одной из специфических строк. HttpClient явно типизирован таким образом, чтобы компилятор мог сообщить правильный возвращаемый тип на основе предоставленных вами опций.

Используйте as const, чтобы дать понять TypeScript, что вы действительно хотите использовать постоянный тип строки:

1
2
3
4
const options = {
    responseType: 'text' as const,
};
client.get('/foo', options);

Чтение полного ответа

В предыдущем примере вызов HttpClient.get() не указывал никаких опций. По умолчанию он возвращает данные в формате JSON, содержащиеся в теле ответа.

Вам может понадобиться больше информации о транзакции, чем содержится в теле ответа. Иногда серверы возвращают специальные заголовки или коды состояния для указания определенных условий, важных для рабочего процесса приложения.

Сообщите HttpClient, что вам нужен полный ответ с помощью опции observe метода get():

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

Теперь HttpClient.get() возвращает Observable типа HttpResponse, а не просто JSON данные, содержащиеся в теле.

Метод компонента showConfigResponse() отображает заголовки ответа, а также конфигурацию:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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! };
    });
}

Как вы можете видеть, объект ответа имеет свойство body правильного типа.

Комментарии