Skip to content
Aprende Angular de forma rápida y efectiva  Ver curso

HttpClient VS Http – El nuevo servicio http de Angular

En Angular 4.3, se lanzó una nueva versión del cliente http, llamada HttpClient, que ha dejado deprecado el servicio Http original. Imagino que estás ansioso, así que voy a explicarte sus novedades. De paso, también te comento una bondad adicional que añade ahora Angular 5…

Novedades

Aquí tienes las principales novedades que trae HttpClient con respecto al servicio Http original de Angular.

Introducción a HttpClient

HttpClient es un cliente con los métodos REST habituales (a continuación), que está basado en Observables. Básicamente es lo que vas a utilizar para hacer llamadas a una API REST y obtener resultados de la misma.

Estos son los métodos de HttpClient:

get(url: string, options: {...}): Observable<any>
delete(url: string, options: {...}): Observable<any>
patch(url: string, body: any|null, options: {...}): Observable<any>
post(url: string, body: any|null, options: {...}): Observable<any>
put(url: string, body: any|null, options: {...}): Observable<any>

head(url: string, options: {...}): Observable<any>
options(url: string, options: {...}): Observable<any>
jsonp<T>(url: string, callbackParam: string): Observable<T>

request(first: string|HttpRequest<any>, url?: string, options: {...}): Observable<any>

Otros detalles que vale la pena recordar:

  • La suscripción se cancela al completar la petición, así que no tienes que gestionarlo tú.
  • Devuelve cold observables
  • No se ejecuta ninguna request hasta que no se llama al método subscribe()
  • Los objetos HttpRequest, HttpHeaders y HttpParams son inmutables.

Hechas las presentaciones… vamos a ver las novedades.

JSON como respuesta por defecto

El nuevo servicio HttpClient te devuelve directamente el body de la respuesta, parseado como JSON. Esta aproximación simplifica el código en la mayoría casos. Eso sí, si trabajas con otro tipo de respuesta, tendrás que escribir algo más de código.

Déjame mostrarte una comparativa con el servicio antiguo.

//old Angular Http service
http.get('/api/items')
.map(res => res.json())
.subscribe(data => {
    console.log(data['someProperty']);
});

Como ves, antes tenías que mapear el resultado para convertirlo en JSON. Ahora ya no es necesario.

//new Angular HttpClient service
http.get('/api/items')
.subscribe(data => {   // data is already a JSON object
    console.log(data['someProperty']);
});

¿Y qué pasa si quiero recibir otro tipo de respuesta?

Los métodos REST de HttpClient aceptan un tercer parámetro (segundo en el caso get y delete) para detallar algunas opciones. Entre ellas, la propiedad responseType para indicar que se espera otro formato.

Las responseType que tienes son:

  • arraybuffer
  • blob
  • json (es la opción por defecto)
  • text

Funcionaría así:

//new Angular HttpClient service
http.get('/api/items', {responseType: 'text'})
.subscribe(data => {   // data is a string
    console.log(data);
});

¿Y si quiero acceder a algo más que el body?

Esta situación también está contemplada. Imagina que quieres acceder a los headers. Puedes acceder a la respuesta completa de la petición con la opción {observe:'response'}.

Así:

//new Angular HttpClient service
http.get('/api/items', {observe: 'response'})
.subscribe(response => {   // data is of type HttpResponse<Object>
    console.log(response.headers.get('X-Custom-Header');
    console.log(response.body['someProperty']); //response.body is a JSON
});

Respuestas tipadas

HttpClient saca provecho de los generics de TS para tipar las respuestas. Si esperas un JSON con una estructura especifica, puedes indicarlo y trabajar cómodamente con tus tipos.

Mira el ejemplo:

//new Angular HttpClient service

interface ItemsResponse {
  results: string[];
}

http.get<ItemsResponse>('/api/items').subscribe(data => {
  console.log(data.results); // OK, data is an instance of ItemsResponse
  console.log(data.test); // Linter & compiler error, test is not a property of ItemsResponse

});

Interceptors

Una de las cosas más criticadas del anterior servicio Http es que no disponía de interceptores (a diferencia de en AngularJS).

Los interceptores son un tipo de middleware que actúa de proxy entre tu cliente y el servidor. Gracias a ellos, puedes añadir funcionalidades genéricas a tu comunicación HTTP.

- No lo pillo, ponme un ejemplo.
- ¡Claro, para eso estoy aquí!

Un caso de uso muy habitual de los interceptors es el de añadir un token de autenticación a todas las peticiones. De este modo, centralizas la lógica en tu interceptor, y tus peticiones REST quedan mucho más limpias.

Otro ejemplo sería el de cachear las respuestas y servir desde el interceptor la respuesta de cache mientras se espera la respuesta del servidor, y una vez recuperada del servidor, actualizar la respuesta del cliente.

- OK, mola, ¡ponme uno!
- Como te conozco…

Un interceptor es una clase que implementa la interfaz HttpInterceptor.

Esta interfaz dispone de un método intercept, en el que se recibe la petición (HttpRequest) y una referencia al siguiente interceptor en la cadena (HttpHandler). Tienes que mover la request adelante para que pase por toda la cadena de interceptores y llegue a enviarse al servidor.

Aquí tienes un ejemplo simple de interceptor que incluye el token de autenticación:

//AuthInterceptor.ts

//...some imports...
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
   constructor(private auth: MyAuthService) {}

   intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
       // Get the auth header from your auth service.
       const authToken = this.auth.getAuthToken();
       const authReq = req.clone({headers: req.headers.set('Authorization', `Token ${authToken}`)});
       return next.handle(authReq);
   }
}

Como ves, el interceptor en sí es muy simple. En el método intercept…
1. recojo el token de autenticación de un servicio interno
2. clono la petición con el header modificado
3. y propago la petición clonada (en lugar de la original) con next.handle().

Aquí hay un par de detalles interesantes, relacionados con algo que decía al principio del artículo…

Los objetos HttpRequest, HttpHeaders y HttpParams son inmutables.

No puedes modificar el objeto HttpRequest y propagarlo tal cual. Como se trata de un objeto inmutable, tienes que crear un nuevo objeto a partir del actual e incluir las modificaciones. Eso es lo que hago en req.clone().

Sucede lo mismo con req.headers. También es un objeto inmutable. Si te fijas bien no modifico primero los headers y luego paso el objeto modificado. Estoy pasando directamente el resultado de req.headers.set(), porque HttpHeaders.set() no modifica el objeto req.headers, sino que devuelve uno nuevo con la propiedad que has añadido.

OK. Tengo el interceptor… y ahora… ¿como lo uso?

Tienes que decirle a Angular que añada este interceptor al array de interceptores http. Esto lo haces en los providers de tu módulo principal:

//app.module.ts

//...some imports...
import {NgModule} from '@angular/core';
import {HTTP_INTERCEPTORS} from '@angular/common/http';

@NgModule({
  providers: [
      {
            provide: HTTP_INTERCEPTORS,
            useClass: AuthInterceptor,
            multi: true,
     }
  ],
  //...more stuff...
})
export class AppModule {}

Para cada interceptor que quieras añadir, tendrás que pasar un objeto como el anterior.

La propiedad multi:true, por cierto, es importante. Es lo que le indica a Angular que HTTP_INTERCEPTORS es en realidad un array al que hay que incorporar el provider que indicas en useClass.

Eventos de progreso

El servicio HttpClient te permite conocer el progreso tanto de la subida de tus peticiones, como de la descarga de sus respuestas. Para eso, dispones de la opción reportProgress=true.

Puedes usar esta opción directamente en el método HttpClient.request(). No obstante, si quieres usar métodos específicos como HttpClient.get() o HttpClient.post(), tendrás que acompañarla de la opción observe="events", para recibir los eventos que permiten detectar el progreso.

Vamos con el código:

//new Angular HttpClient service
http.get('/api/items', {observe: 'events', reportProgress: true})
    .subscribe(event=>{
      if (event.type === HttpEventType.DownloadProgress) {
        console.log(event.loaded); //downloaded bytes
        console.log(event.total); //total bytes to download
      }
      if (event.type === HttpEventType.UploadProgress) {
        console.log(event.loaded); //uploaded bytes
        console.log(event.total); //total bytes to upload
      }
      if (event.type === HttpEventType.Response) {
        console.log(event.body);
      }
    });

Como ves, de forma sencilla puedes comprobar el número de bytes recibidos y la progresión con respecto al total.

RxJS 5.5

No es algo propio del servicio HttpClient (Angular 4.3+), pero sin duda está relacionado. Con el nuevo Angular 5, se usa por defecto una versión más actual de RxJS para tratar con Observables, la versión 5.5.

Esta actualización de RxJS tiene una mejora muy importante en términos de optimización (code splitting / tree shaking) pero también en términos de usabilidad, los lettable operators.

Básicamente, esta nueva versión de RxJS utiliza los módulos de ES6 de la forma habitual, permitiéndote importar operadores de forma individual desde una librería común.

Me explico.

Antes, para importar los operadores map y filter, por ejemplo, tenías que importarlos cada uno de su propio archivo. Algo muy incómodo:

//rxjs old style
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/filter';
names = allUserData
.map(user => user.name)
.filter(name => name);

Sin embargo, con RxJS 5.5, puedes importarlos directamente de rxjs/operators. Eso sí, ya no puedes encadenar los operadores con ., sino que tienes que hacerlo mediante el nuevo método pipe (este es el cambio que se ha hecho por temas de optimización).

//rxjs 5.5 lettable operators
import { Observable } from 'rxjs/Observable';
import { map, filter } from 'rxjs/operators';
names = allUserData.pipe(
  map(user => user.name),
  filter(name => name),
);

Wow, todo esto parece interesante y quiero probarlo pero…

…¿como empiezo?

Usando HttpClient

El servicio HttpClient pertenece al módulo HttpClientModule, ambos expuestos en la librería @angular/common/http.

Para usarlo tienes que instalar dicha librería con npm o similar. Si usas una versión reciente de Angular CLI, ya lo instala al crear tu proyecto.

#terminal
npm install @angular/common/http

Una vez tienes la librería, debes importar HttpClientModule desde el módulo principal.

// app.module.ts:

//...some other imports...
import {HttpClientModule} from '@angular/common/http';

@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule, // Include it after BrowserModule
    //...some more imports...
  ],
})
export class MyAppModule {}

El resto es coser y cantar. Importa tu servicio HttpClient, inyéctalo por DI, y ya estarás listo para usarlo.

//...some other imports...
import {HttpClient} from '@angular/common/http';

@Component(...)
export class MyComponent implements OnInit {

  constructor(private http: HttpClient) {}

  ngOnInit(): void {
    //try some HTTP request:
    this.http.get('some/url').subscribe(data => {
        console.log(data);
    });
  }
}

Conclusiones

He oído todo tipo de opiniones sobre este servicio. Unos muy felices, sobretodo por los interceptors y otros indignados por las respuestas JSON por defecto.

Disponer de interceptores es un punto positivo, pero también lo es la gestión del progreso e incluso el tipado de respuestas. Si tu servidor devuelve un formato distinto al JSON, siempre puedes indicarlo e incluso crearte un wrapper para no tener que repetir siempre la operación.

¿Mi veredicto? Sin duda, un cambio a mejor. ¿Y tú, qué opinas?

Si te ha gustado este artículo, compártelo 😉

Published inAngularJavascriptTypeScript

27 Comments

  1. Carlos Carlos

    Excelente información, Feliz Navidad…

    Te escribo ya que buscado ayuda acerca de un problema que tengo al implementar HttpCliente, erese el único que tienes la información reciente.

    Acciones realizadas
    Baje la ultima versión de angular 5.1.3-build.38002+sha.12702b2.

    Declare en app.module httpClientModule
    en el componente dentro del construct (private http:HttpClient)
    En el componente declare:
    import { Observable } from ‘rxjs/Observable’;
    import ‘rxjs/add/operator/map’;
    import ‘rxjs/add/operator/filter’;
    import {HttpClient} from «@angular/common/http»;

    En la función tengo es
    this.http.get(‘http://localhost:8500/imagen’).subscribe(data => {
    console.log(data);
    });

    El error es el siguiente

    StaticInjectorError[Http]:
    StaticInjectorError[Http]:
    NullInjectorError: No provider for Http!
    at _NullInjector.get (core.js:993)
    at resolveToken (core.js:1281)
    at tryResolveToken (core.js:1223)
    at StaticInjector.get (core.js:1094)
    at resolveToken (core.js:1281)
    at tryResolveToken (core.js:1223)
    at StaticInjector.get (core.js:1094)
    at resolveNgModuleDep (core.js:10878)
    at NgModuleRef_.get (core.js:12110)
    at resolveDep (core.js:12608)

    Agradezco tus comentario…

    • Enrique Oriol Enrique Oriol

      Pues o no estás inyectándolo en el componente, o no estás importándolo correctamente su módulo, por que no encuentra el provider para la inyección de dependencias.

    • Enrique Oriol Enrique Oriol

      Pues o no estás inyectándolo en el componente, o no estás importándolo correctamente en su módulo, por que no encuentra el provider para la inyección de dependencias.

    • debes importar el HttpClient en app.module.ts y agregarlo en providers

  2. Hola Enrique,

    Gracias por el post. Mi granito de arena:

    Es importante que el AuthServide que usamos en el Interceptor no haga llamadas http, ya que caeria en un loop infinito si las hace. El error que muestra es «Cannot instantiate cyclic dependency! InjectionToken_HTTP_INTERCEPTORS («[ERROR ->]»): in NgModule AppModule in ./AppModule@-1:-1″

    Mas info aquí: https://github.com/angular/angular/issues/18224

    He estado varias horas hasta que he dado con la tecla..

    • Enrique Oriol Enrique Oriol

      Hola Antonio,

      Si lo que quieres es lanzar una nueva petición desde el interceptor, aunque es poco habitual, puedes hacerlo clonando otra vez la petición, cambiándole la URL para que encaje con la nueva URL que quieres y devolviendo un merge de los 2 observables resultantes de next.handle.

      ¿Para que escenario estás contemplando generar una petición desde el interceptor exactamente?

      Saludos,

  3. Dani Dani

    Hola Enrique,
    Solo quería dejarte en comentarios que en un proyecto sencillo (mi nivel sigue en newbie) y estoy realizando varios Interceptors (para procesar XML de FeedBurner, para añadir parámetros al header) y tu artículo me lo he leído reiteradas veces y es a donde voy cada vez que tropiezo ..que es constantemente. Pues eso.. esperamos más post instructivos sobre el tema.
    Gràcies company!

    • Enrique Oriol Enrique Oriol

      Gracias, me alegro de echar un cable 🙂

      A ver cuando tengo tiempo para escribir el siguiente!!!

  4. Fran Fran

    Veo que tu this.auth.getAuthToken(); es sincrono, pero como lo harias si fuera asincrono ¿? si lo tuvieras que coger del storage de ionic por ejemplo.

    Se puede hacer? Gracias.

  5. Fernando Fernando

    Gracias tío estoy creando una app de introducción y me has salvado la vida con tus ejemplo, si es que joder, eres un crack.

    • Enrique Oriol Enrique Oriol

      XD ¡¡gracias!! Me alegro de que sea útil 😉

  6. antalvpro antalvpro

    Cuando agrego el subscribe me arroja este error:

    «El tipo ‘Subscription’ no se puede asignar al tipo ‘Observable’.
    Falta la propiedad ‘_isScalar’ en el tipo ‘Subscription’.»

    No se a que se debe si puedes aclararme esto seria de mucha ayuda, gracias 🙂

  7. Jesus Jesus

    Hola Enrique, tengo un problema y es que estoy intentando obtener un token de autorización a un servidor de spring boot (oauth2) para el ingreso a la aplicación, pero no se porque se ejecuta dos veces la petición una con POST y otra con OPTIONS, la primera que se ejecuta es la OPTIONS y el servidor rechaza esa petición.

    Se puede deshabilitar esa peticón?.

    Quedo atento.

    Gracias.

    • Enrique Oriol Enrique Oriol

      ¡Gracias!

  8. Hola Enrique, muy buen articulo. Llegue aquí buscando solución a un problema que tengo en mi código, estoy utilizando angular 5 y estoy consumiendo un servicio en .NET. El http.post me funciona muy bien el problema es cuando hago uso del http.get para realizar búsquedas ya que necesito pasar un JSON como parámetro, es decir si quiero traer todos los países que empiecen con «G» de latinoamerica por ejemplo.

    le dejo mi servicio aqui.

    getPaises(pais: Pais){

    let data = JSON.stringify(pais);

    //let headers = new Headers({«Content-Type»: «application/json; charset=utf-8»});

    return this._http.get(this.server + this.url+ this.metodo, data)
    .map(res => res.json());

    }

    MI COMPONENT

    onSubmit(){

    this._paisService.getPaises(this.paises).subscribe(
    response => {

    if(response.code != 200){
    console.log(response);
    }else{
    this.paises = response.data;
    this._router.navigate([«/pais-list»]);
    }
    },
    error => {
    console.log(error);
    alert(‘Error… ‘ + error);
    }
    );
    }

    Gracias por su apoyo, saludos.

  9. Christian Christian

    Buenas Enrique, muy buen artículo. una pregunta. Si tenemos una llamada a una API (e mi caso varias), la creaciónn de interfaces, es muy tediosa, y eternas. Es posible no hacer Interface, cuando puede ir cambiando la estructura de la/las API’s?

    Un saludo.
    Christian

  10. Daniel Daniel

    No entiendo los ejemplos, Angulat 7, no usa eso !

    • Enrique Oriol Enrique Oriol

      Este artículo es de 2017, y hasta ese momento, Angular utilizaba un cliente http que se llamaba Http.

      Ese servicio se deprecó y se reemplazó por un nuevo servicio llamado HttpClient, que es el mismo que usa ahora Angular 7.

  11. Daniel Daniel

    Les dejo una consulta,que no he podido resolver.
    Intento filtrar un json usamdo lo siguiente:

    searchProducto(term: string): Observable {
    if (!term.trim()) {return of([]);}
    let headers = new HttpHeaders().set(‘Content-Type’, ‘application/json’);
    let params = new HttpParams().set(‘Gpclave’, term );
    return this.http.get(`${ubi}/`,{params, headers})
    .pipe( retry(2), tap(listaDemosp => console.log(listaDemosp) ) )
    }

    Si term=Pedo, me trae los datos de Pedro; pero si termm=Pe, no me trae nada
    alguna yudita porfa !!!!!!!!

  12. Julian Julian

    Hola!

    Ayuda!!: tengo un problema con hacer varias llamadas http.get() y las suscripciones.

    resulta que tengo un componente que requiere varios request independientes uno de otros para obtener información, consultarServicios(), consultarFacturas(), y consultarProductos(). todas llaman al servicio de httpClient.get y luego cada método se subscribe para poder generar su modelo. ahora bien cuando corro la app en alguno caso funciona bien y en otros no.

    si en vez de hacer las llamadas independientes a cada método lo pongo dentro del subscriptor de cada uno, llamarlos de manera encadenada anda perfectamente. porque sucede esto? porque no puedo llamar de manera independiente a cada uno?

    //ESTA ES LA FORMA QUE NO FUNCIONA
    ngOnInit() {
    this.consultarServicios()
    this.consultarProductos();
    this.consultarFacturas();
    }

    private consultarServicios(){
    console.log(«– consultarServicios –«);
    this.webservice.getServicio().subscribe((data: Servicio) =>{
    this.servicio = new Servicio(data);
    },error => {
    console.log(error);
    });
    }

    private consultarProductos(){
    console.log(«– consultarProductos –«);
    this.webservice.getFacturas().subscribe( (data: Productos) =>{
    this.productos = data;
    },error => {
    console.log(error);
    });
    }

    private consultarFacturas(){
    console.log(«– consultarFacturas –«);
    this.webservice.getFacturas().subscribe( (data: Facturas) =>{
    this.facturas = data;
    },error => {
    console.log(error);
    });
    }

    //Y ASI SI ESTA FUNCIONADO CORRECTAMENTE
    ngOnInit() {
    this.consultarServicios();
    }

    private consultarServicios(){
    console.log(«– consultarServicios –«);
    this.webservice.getServicio().subscribe((data: Servicio) =>{
    this.servicio = new Servicio(data);
    this.consultarProductos(); // < {
    console.log(error);
    });
    }

    private consultarProductos(){
    console.log(«– consultarProductos –«);
    this.webservice.getFacturas().subscribe( (data: Productos) =>{
    this.productos = data;
    this.consultarFacturas(); // < {
    console.log(error);
    });
    }

    private consultarFacturas(){
    console.log(«– consultarFacturas –«);
    this.webservice.getFacturas().subscribe( (data: Facturas) =>{
    this.facturas = data;
    },error => {
    console.log(error);
    });
    }

  13. Leynier Leynier

    Estoy usando @angular/cli»: «^8.1.2» y «bootstrap»: «^4.3.1 y me da error al llamar al metodo .subscribe

    addZona(from: NgForm) {
    this.zonaService.postZona(from.value);
    from.reset();
    .subscriber(res => {
    console.log(res)
    })
    }

  14. Victor hugo Enriquez Victor hugo Enriquez

    un cordial saludo.. tengo una app que hace una llamada a una api que no tiene protocolo ssl en mi maquina local todo va bien pero al subirla a mi servidor sobre un dominio que si tiene protocolo ssl entran en conflicto de contenido mixto, como resolverlo?, como hacer que angular reciba datos desde una api sin ssl?

  15. Vanessa Vanessa

    Holaa que tal tengo un un problema con los Headers,

    async getEstado(){
    await this.usuarioS.cargarToken();

    const Myheaders = new HttpHeaders({
    ‘x-token’: this.usuarioS.token,
    // ‘a-token’: this.agendaService.tokenA
    });

    console.log(Myheaders); // En este no me llega el header, lo recibe nulo
    console.log(this.usuarioS.token); // Y en este me imprime el token

    Mi error ahi

    https://i.stack.imgur.com/eyBfP.png.

    Hice este código, porque mis cabeceras son personalizadas. Lo extraño es que tengo este mismo codigo en otro servicio y si me funciona, SOLO en este me llega nulo.

  16. Cristhian Montoya Cristhian Montoya

    Hola, una consulta, tengo un tag img con la url de imagen correspondiente en el attribute src. La imagen solo puede ser accedida mediante request GET, siempre y cuando le pase el token de autorización, ¿cómo podría hacer un interceptor que agregue el token automáticamente sin hacer una petición tipo httpClient?

  17. Victor Hugo Victor Hugo

    Hola, muy interesante el artículo, me surge la duda de si es posible utilizar interceptor para recuperar csrfToken de la respuesta de una API al método GET?, por que la necesito para pasarla por header en un POST posterior, pero no se como accederlo, como acceder a las cookies que retorna. si por las dudas tenes algún artículo al respecto o sabes donde puedo encontrar algo al respecto

Deja un comentario