Самый распространенный способ получить данные из web служб — это через Http. И в этой статье мы посмотрим как это можно сделать Http-запрос в Angular 4.3 через новый HttpClient.


Начиная с версии Angular 4.3 появился новый HttpClient. В этой статье описывается только новый клиент.


Angular > 4.3


HttpClientModule
HttpClient


Angular <= 4.3


HttpModule
Http


Замечание: Старая реализация http все еще существует в 4.3, поэтому убедитесь, что вы используете правильный.


Импорт модуля


Прежде всего, нам нужно импортировать HttpClientModule в родительский модуль. Если у вас новый Angular проект, то это скорее всего будет AppModule. Чтобы импортировать модуль, просто добавьте его в раздел imports родительского модуля.


src/app/app.module.ts


import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
// Убедитесь, что вы используете именно этот импорт, чтобы использовать новую версию модуля
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    HttpClientModule // Импортируем модуль
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Создаем простой сервис


Правильным подходом при построении Angular приложении считается вынос всех HTTP-запросов из компонентов в сервисы, чтобы сделать их полностью независимыми, а все запросы завернуть в отдельные сервисы. Потом например, при тестировании можно будет сделать mocke версию сервиса. Да и код при таком подходе получается более аккуратным.


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


У каждой службы есть определенная цель. Например, если мы хотим запросить фотографии кошек через http, то наша служба будет называться CatPictureService. Основа сервиса будет выглядеть примерно так:


src/app/services/catPicture.service.ts


import { Injectable } from '@angular/core';

@Injectable()
export class CatPictureService {

constructor() { }

}

Чтобы сделать http-запрос из нашего сервиса, нам нужен Angular HttpClient. Мы можем его легко добавить посредством инъекции зависимостей (dependency injection, DI).


src/app/services/catPicture.service.ts


import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable()
export class CatPictureService {

constructor(private httpClient: HttpClient) { }

}

Создание запроса


Чтобы сделать запрос, мы добавим новый метод в наш сервис. Методы обычно называются глаголом. На данный момент мы назовем метод «get». Он получит по URL-адресу фотографию кота.


public get(url: string): Observable<any>{
    return this.httpClient.get(url);
}

Как вы можете видеть, Angular HttpClient довольно прост. Все, что нам нужно сделать, это вызвать метода get и передать ему url. Данный метод get возвращает объект Observable. Этот класс является частью библиотеки rxjs, которая используется во многих местах Angular'а. Одним из примеров использования является HttpClient.


Подобно обещанию(Promise), наблюдатель(Observable) не содержит в себе сразу значения. Вместо этого у него есть метод подписки(subscribe), где мы можем зарегистрировать обратный вызов(callback). Этот callback вызывается, как только результат будет доступен. Помимо обещания, Observable может вернуть более одного значения. Вы можете вернуть себе поток результатов. Но это не имеет значения для этого урока. В нашем случае Observable возвращает только одно значение.


Подписка на наблюдатели (Subscribing to Observables)


Итак, как мы можем подписаться на наблюдателя, которого вернул наш новый метод, чтобы получить фактическое значение? Это довольно легко. Мы просто вызываем метод подписки subscribe и регистрируем один (или два) метода для обратного вызова. Первый обратный вызов вызывается, когда результат доступен. Он получает результат как параметр. Второй обратный вызов запускается, когда с запросом возникает какая-либо ошибка. В качестве параметра он получает объект ошибки.


Вот как это выглядит в коде. Я составил экземпляр picService. Вам необходимо запровайдить и запросить этот сервис самостоятельно.


this.picService.get('http://anyurl.com').subscibe(value =>{
    // value - результат
},
error => {
    // error - объект ошибки
});

ПРИМЕЧАНИЕ. Вам всегда нужно подписываться(вызывать метод subscibe) в противном случае запрос не будет сделан!


Опции


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


Форматы ответов


Начиная с Angular 4.3, формат ответа по умолчанию — JSON. Это делает использование HttpClient очень простым. Вам больше не нужно анализировать ответ вручную. Angular делает это для вас.


Хотя JSON является наиболее распространенным форматом ответов, есть много примеров, когда вы не можете использовать JSON. Например, при запросе фотографий кошечек.


Чтобы избежать автоматического анализа ответа, мы можем определить заранее свойство responseType через объекта options.


{ responseType: 'text' }

Заголовки


Чтобы добавить заголовки в запрос, мы используем свойство headers объекта options. Для этого нам нужен объект HttpHeaders. Он содержит все наши определения заголовков. Не используйте объект Headers, так как он является частью старого клиента Http.


const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
const options = { headers: headers };

URL параметры


Мы также можем определить параметры url внутри объекта options. Для этого нам нужно создать объект HttpParams. Таким образом, нам не нужно добавлять их в строку url.


const params = new HttpParams().set('id', '3');
const options = {params: params};

Отслеживание прогресса


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


Для этого нам нужно разбить наш запрос в отдельном объекте запроса. Чтобы получить прогресс, нам нужно установить для свойства reportProgress значение true.


import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { HttpRequest } from "@angular/common/http";
import { Subject } from "rxjs/Subject";
import { HttpEventType } from "@angular/common/http";
import { HttpResponse } from "@angular/common/http";

public post(url: string, file: File): Observable<number>{

    var subject = new Subject<number>()
    const req = new HttpRequest('POST', url, file, {
        reportProgress: true,
    });

    this.httpClient.request(req).subscribe(event => {
        if (event.type === HttpEventType.UploadProgress) {
            const percentDone = Math.round(100 * event.loaded / event.total);
            subject.next(percentDone);
          } else if (event instanceof HttpResponse) {
            subject.complete();
          }
    });
    return subject.asObservable();
}

Метод post возвращает объект наблюдателя(Observable), представляющий ход загрузки.


Перехватчики (Interceptors)


Еще одной замечательной особенностью нового HttpClient являются перехватчики. В некоторых случаях вам может потребоваться изменить запрос до того, как он попадет на сервер. Или вы хотите изменить каждый ответ. Вы можете сделать это с помощью Interceptor. Это своего рода промежуточное ПО между http-api и фактическим запросом.


Одним из распространенных вариантов использования может быть аутентификация. Чтобы получить ответ с сервера, вам часто нужно добавить какой-то механизм проверки подлинности в запрос. Конечно, вы можете просто изменить заголовок авторизации в своей службе. Но эта задача всегда одна и та же, не так ли? Это всегда один и тот же протокол. Он никогда не меняется, даже между приложением. Так почему бы нам не написать логику один раз и повторно использовать ее повсюду?


Определение перехватчика


Подобно сервису, перехватчик является инъекционным(Injectable).


import { Observable } from 'rxjs/Observable';
import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';

@Injectable()
export class AuthenticationInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
      req.headers.append('Authorization', '<SOME-TOKEN>')
    return next.handle(req);
  }
}

Поскольку приложение может иметь несколько перехватчиков, они организованы в цепочку. Первый элемент вызывается Angular'ом. Впоследствии мы несем ответственность за передачу запроса следующему перехватчику. Чтобы это сделать, мы вызываем метод handle следующего элемента в цепочке, как только мы закончим.


Прежде чем мы это сделаем, мы можем изменить запрос, как нам нравится. Например, добавьте токен в заголовок авторизации.


Этот пример отнюдь не является полным или многоразовым. Но это должно дать вам представление о том, как можно отсюда продолжить.


Представление перехватчиков


Так же, как сервисы, мы также должны запровайдить перехватчики. Для этого ваш родительский модуль (AppModule) должен выглядеть примерно так:
src/app/app.module.ts


import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
// Убедитесь, что вы используете именно этот импорт, чтобы использовать новую версию модуля
import { HttpClientModule } from '@angular/common/http';
import { HTTP_INTERCEPTORS } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    HttpClientModule // импорт модуля
  ],
  providers: [{
    provide: HTTP_INTERCEPTORS,
    useClass: AuthenticationInterceptor,
    multi: true,
  }],
  bootstrap: [AppComponent]
})
export class AppModule { }

Итого


Теперь мы знаем, как можем отправлять или получать внешние данные через http. Мы также узнали, какие варианты мы можем использовать и как отслеживать прогресс.


Надеюсь, вам понравилась эта статья.

Комментарии (17)


  1. x07
    24.08.2017 08:40
    +1

    Описано все тоже самое, что и в старом. Чем новый лучше старого?


    1. nomoreload
      24.08.2017 09:05

      Interceptor'ы- киллер фича. По сути middleware, который работают как на отправку, так и на получение. Отличная штука. Не надо больше сервисы-обвязки городить для того, чтобы токены в заголовки проставлять.


      1. DAiMor
        24.08.2017 10:24
        +3

        Раньше это тоже вполне работало, в частности если нужно было токен добавить.

        @Injectable()
        export class AuthRequestOptions extends BaseRequestOptions {
          merge(options?: RequestOptionsArgs): RequestOptions {
            let newOptions = super.merge(options);
            newOptions.headers.set('Authorization', 'Bearer SOME.TOKEN');
            newOptions.merge = this.merge;
            return newOptions;
          }
        }
        
        и
        { provide: RequestOptions, useClass: AuthRequestOptions }


      1. Apx
        25.08.2017 02:03

        А в 1.х ангуляре их что-ли не было?


    1. Poccomaxa_zt
      24.08.2017 10:45
      +1

      Можете заглянуть по этой ссылке — github.com/angular/angular/commit/37797e2.
      Кратко — избавляет Вас от необходимости использовать сервисы обёртки, которые разработчики пишут каждый по своему…
      Из часто используемого — благодаря релизу теперь у Вас есть тип ответа JSON по умолчанию, и возможность обработки запросов (добавление хедеров, обратка ошибок и тд) более структурированно…


    1. Apx
      25.08.2017 02:02

      Описано как в документации…


  1. xGromMx
    24.08.2017 13:45

    Который раз вижу, что используют сабжект неправильно! Зачем вы такое делаете?


    1. Hooter Автор
      24.08.2017 14:44

      Извините, но что Вы имеете в виду? Статья содержит базовые примеры с акцентами на изменения, которые привнесли в HttpClient. И эти примеры мало чем отличаются от официальной документации.


  1. kemsky
    24.08.2017 20:51

    Первая версия была очень ограниченной, в итоге пришлось на писать свою обертку над XHR, со всеми интерсепторами, терпимой поддержкой аплоада и прочим. А эта вторая версия умеет загружать файлы (с мониторингом прогресса)?


    1. Hooter Автор
      24.08.2017 21:06

      В статье как раз пример мониторинга прогресса с загрузкой на сервер HttpEventType.UploadProgress, при загрузке с сервера было бы HttpEventType.DownloadProgress.

      Пока что единственное с чем еще не разобрался, это как сделать фейковый бекенд, на Http все делалось через MockBackend. Здесь же можно легко протестировать, а вот отдельный бекенд как то не собирается.


      1. kemsky
        24.08.2017 21:54

        Похоже они решили вместо того, чтобы просто принимать Subject<HttpEvent>, дать низкоуровневый апи request, который выдает все события. Это лучше чем ничего.


        Правда HttpEventType не соответсвует XHR событиям, как понять что случился таймаут или ошибка соединения? Какого типа свалится еррор в сабскрайбера?


        Еще один вопрос, клиенту попрежнему нельзя сказать какие статус коды являются успешными, а какие нет?


        1. nomoreload
          24.08.2017 22:54

          Можете подписаться на все события, мониторить окончание запроса и в зависимости от результата смапить его в результат, либо в ошибку. Ну то есть в зависимости от требуемого кейса, успешным может быть не только 200, но и 201, например.


      1. DAiMor
        24.08.2017 23:59

        А с какой целью нужен фейковый бекенд? Для использования в тестах?
        В этой статье вроде рабочий пример тестов с новым HttpClient.
        Ну и конечно поиск по github. Отличный источник готовых примеров, с учетом распространнености Angular, то довольно быстро появляются примеры.


        1. Hooter Автор
          25.08.2017 11:05

          Особенность работы HttpTestingController в том что он вызывается сразу после самого запроса. Для тестирования так как раз и нужно. А для моих целей не совсем. Мне как раз бы лучше развести это все дело в разные места.
          А я использую фейковый для эмуляции работы бекенда, пока нет самого бекенда, или пока он не поднят на environment. При этом учитывая, что фронт будет загружен на amazon и там же как то будет, потом, бекенд, то собирать временный бекенд из чего-то другого смысла нет.
          Вот на старом было удобно сварганить быстренько API бекенда, чтобы показать работу системы, и по готовности бекенда переводить UI на нормальную работу, исправляя конфиг с endPoint'ами.


          1. DAiMor
            25.08.2017 11:40

            Ok, a in-memory-web-api чем не подходит? Я симуляцию бека с ним делал, в том числе там есть поодержка простеньких фильтров в запросе.


            1. Hooter Автор
              25.08.2017 12:34

              Он как раз на Http, а не на HttpClient. На Http можно и проще сделать. Простой пример fake-backend. Сейчас пробую через CachingInterceptor собрать для HttpClient.


            1. MOTORIST
              25.08.2017 13:33

              Мне больше нравится json-server. На in-memory-web-api не смог настроить singular routes.