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.
- JSON como respuesta por defecto
- Respuestas tipadas
- Interceptors
- Eventos de progreso
- RxJS 5.5 (en Angular 5)
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
yHttpParams
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 😉
Yo también pienso que es un cambio a mejor. Gracias por el artículo; muy interesante como siempre 😉
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…
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.
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
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..
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,
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!
Gracias, me alegro de echar un cable 🙂
A ver cuando tengo tiempo para escribir el siguiente!!!
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.
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.
XD ¡¡gracias!! Me alegro de que sea útil 😉
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 🙂
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.
Genial amigo, muy buena info, me ha sido de utilidad , sigue asi
¡Gracias!
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.
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
No entiendo los ejemplos, Angulat 7, no usa eso !
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.
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 !!!!!!!!
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);
});
}
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)
})
}
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?
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.
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?
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
Lo que no habia podido encontrar en nigún foro era la implementación de un servicio con angular 15 y es tan facíl como esto:
import { Injectable } from «@angular/core»;
import { HttpClient } from ‘@angular/common/http’;
@Injectable()
export class PeticionesService{
public url:string;
constructor(private _http:HttpClient){
this.url=»https://jsonplaceholder.typicode.com/posts»;
}
getPrueba(){
return ‘Hola Mundo desde el servicio’;
}
getArticulo(){
return this._http.get(this.url);
}
}