Un lector me preguntaba hace unos días… ¿que diferencias hay entre el servicio $http
de AngularJS y el servicio Http
de Angular 2? Voy a tratar de resolver la duda.
Diferencias entre los servicios http de AngularJS y Angular 2
La diferencia principal es que el $http
de AngularJS devuelve Promises mientras que el Http
de Angular 2 devuelve Observables. Sí, se que así a bote pronto esto te deja frío. Sigue leyendo, que ya entraremos en materia 😉
Observables vs Promises
Ahora te explico más en detalle que es esto de los observables, pero ya te adelanto 2 diferencias que convierten en claro ganador a la apuesta de Angular 2:
- Los observables se pueden cancelar, a diferencia de las promises
- Las promises solo pueden devolver una respuesta (a menos que combines varias promises con
$q.all
)
El primer punto es muy interesante y puede simplificarte la vida. Te pongo en situación: Imagina que estás navegando por un catálogo de productos. Seleccionas una categoría (se hace una petición GET a una API REST para obtener sus productos), pero te has equivocado de categoría así que vuelves atrás y seleccionas otra antes de que se haya llegado a resolver la primera llamada a la API.
AngularJS no tendría manera de parar la primera petición, así que tendrías que ensuciarte las manos para implementar un mecanismo que permitiera detectar esta situación y asegurarte que cuando recibes ambas peticiones del servidor, solo se cogen los productos de la petición que se corresponde con la vista final.
Los Observables se pueden cancelar, a diferencia de las Promises, lo que aporta una clara ventaja a Angular 2.
Con Angular 2, simplemente cancelarías la primera petición y no tendrías que preocuparte más del tema.
En otros aspectos, en cambio, tienen ciertas similaridades. Los callbacks de éxito/error de Promises se puede encadenar (encadenando las llamadas a then: .then(...).then(...)
). Del mismo modo puedes encadenar callbacks de éxito/error de Observables ya que te puedes suscribir a un Observable tantas veces como quieras (con share
y subscribe
).
Pero… ¿qué son exactamente los famosos Observables?
Programación reactiva
Antes de nada, cabría destacar un cambio importante en la arquitectura de Angular 2 respecto a su predecesor: Angular 2 está orientado a la Programación Reactiva. Esto significa que está diseñando para reaccionar en base a la propagación de cambios de flujos de datos (eventos) asíncronos.
Para que se entienda más este punto, mientras que AngularJS se iba preguntando todo el rato si un objeto había cambiado, en Angular 2 es al revés: Hay objetos que cuando cambian, se lo notifican a aquellos que están interesados en detectar sus cambios (suscriptores). Este tipo de objetos, que se pueden «observar», se denominan Observables.
Mientras AngularJS comprobaba a cada ciclo si un objeto había cambiado, Angular2 se suscribe al objeto para ser notificado cuando éste cambie.
Concretamente, Angular utiliza la librería de flujos asíncronos RxJS en su versión 5 (todavía en beta), que ofrece varias utilidades para trabajar con Observables.
Observables
Para hacerlo simple, podríamos decir que un Observable es un objeto que guarda un valor y que emite un evento a todos sus suscriptores cada vez que ese valor se actualiza. En palabras de la RxJS, un Observable es un conjunto de valores a lo largo de cualquier intervalo de tiempo.
La librería RxJS te aporta además varias operadores para transformar los resultados de tus Observables, como el operador map
(equivalente al método map
de los arrays JS para manipular cada elemento del array), el operador debounce
para ignorar eventos demasiado seguidos, el operador merge
para combinar los eventos de 2 o más observables en uno…
La imagen siguiente presenta un observable que genera valores a lo largo del tiempo así como el resultado que obtienes al suscribirte teniendo en cuenta que el observable ha sido transformado con una función map
para multiplicar sus valores por 10.
Si tienes interés, te recomiendo echarle un vistazo a la web de rxmarbles.com que dispone de unos diagramas interactivos muy intuitivos para describir cada uno de los operadores de Rx.
Usando la librería Http de Angular 2
Ahora que te he explicado las diferencias a nivel conceptual, vamos a ensuciarnos un poco las manos con código para ver como usar esta nueva versión.
Inyectando el servicio
Para poder utilizar el servicio Http
antes que nada tienes que importarlo en el componente o servicio donde lo quieres usar, desde el paquete @angular/http
, y pasarlo por DI.
Veamos un ejemplo:
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
@Injectable()
export class MyComponent {
constructor(private http: Http) { }
}
Es importante destacar que el servicio Http
no pertenece al núcleo de Angular, por lo que hay que importar también su módulo HttpModule
y pasarlo en la fase de bootstrap (Si estas usando Angular 2 desde Ionic 2, esta fase no te afecta, ya lo hace Ionic 2 por ti).
Sería algo del estilo de esto:
//main.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
@NgModule({
imports: [ BrowserModule, HttpModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule{}
GET
Volvamos al ejemplo del catálogo de productos. Imagina que quieres hacer una petición a una API REST para obtener el listado de productos de un catálogo. El código podría tener esta forma:
//productsService.ts
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
@Injectable()
export class ProductsService {
constructor(private http: Http) { }
public categoryId = 0;
get products() {
return http.get(`base_url/category/${categoryId}/products`)
.map(response => response.json());
}
}
Como puedes ver, en el getter de productos, estoy usando el método GET del servicio Http
y le paso únicamente la URL. Además, ejecuto el método map
sobre el Observable que me devuelve la llamada anterior, para coger los datos de la respuesta HTTP en formato JSON.
De este modo, el getter de productos acaba devolviendo un Observable que emite un objeto JSON con un Array de Productos.
Usando servicios que devuelven Observables
¿Por qué es interesante la aproximación que acabo de hacer con el método GET?
Ahora es mi servicio el que devuelve un Observable con los resultados extraídos de la respuesta de la API REST, así que en cualquier componente donde use mi servicio, podré suscribirme a las actualizaciones de la propiedad products
.
Vamos a un ejemplo muy simple en el que mostramos un listado de productos con su precio:
//myApp.ts
import { ProductsService } from './productsService';
@Component({
selector: 'my-app',
template: `<div *ng-for="let product of products">{{product.name}} - {{product.price}}</div>`
})
export class App {
constructor(productsService: ProductsService) {
productsService.products
.subscribe(
products => this.products = products,
error => console.error(`Error: ${error}`)
);
}
}
Esto es un ejemplo claro del uso del paradigma de programación reactiva que usa Angular 2:
- En Angular 1 para conseguir esto mismo usaría un
$watch
, para comprobar a cada ciclo siproductsService.products
ha cambiando. - En Angular 2 en cambio, la estrategia es mucho más eficiente porque es la propiedad
productsService.products
la que al ser actualizada me avisa.
POST
Ahora ya tienes los conceptos básicos, pero voy a mostrarte como hacer una petición POST para completar la información.
Pongamos que en esta misma app, tienes la opción de subir opiniones de los productos.
Una versión muy básica de como podría subirse una opinión a través de una API REST sería enviando la puntuación y la opinión que tienes del producto en el body de la petición POST, en formato JSON (lo indicarás en los headers).
Sería algo así:
//productsService.ts
import { Headers, RequestOptions } from '@angular/http';
//...more imports...
@Injectable()
export class ProductsService {
constructor(private http: Http) { }
//..previous stuff...
sendOpinion(rating:number, description:string, productId:number){
//build header options
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
//build POST body
let body = JSON.stringify({rating, description});
//send data to server
this.http
.post(`base_url/category/products/${productId}`, body, options)
.map(response => response.json())
.subscribe(
data => console.log('Success uploading the opinion ', data),
error => console.error(`Error: ${error}`
);
}
}
En este último ejemplo puedes ver como creo un objeto de tipo Headers
, donde indico que los datos que se van a enviar serán de tipo JSON (aquí se podría pasar otra información como un token de autentificación, por ejemplo), y encapsulo los headers dentro de un objeto RequestOptions
.
Creo también el contenido de la petición y se lo asigno a la variable body
utilizando la abreviatura de propiedades que proporciona ES6.
Finalmente, utilizo http.post
pasándo los parámetros anteriores y me suscribo al observable que devuelve para que realmente se ejecute la petición.
Conclusiones
El nuevo servicio Http de Angular 2 es mucho más potente que su predecesor gracias al uso de Observables.
Si vienes de AngularJS, quizá se te hace un poco extraño y cuesta arriba esto de los Observables, pero te aseguro que vale la pena invertir unos minutos de tu tiempo en aprender a usarlos.
Dime… ¿ya te han entrado ganas de empezar a usar Observables?
Gran artículo Enrique, sería interesante si publicaras más acerca de este tema. Y sobre todo el enfoque que das a tus artículos para cubrir las novedades de Angular 2 daría para mucho HTTP, Router, Programación Reactiva, Servicios, Directivas,…
Solo una cosilla respecto al código creo que falta la definición de las options usadas en el método post, en el último ejemplo de código. Por lo demás está de lujo.
Gracias por ponernos al día.
Gracias Fran y sí, tienes toda la razón, ¡¡me he comido una linea de código!!
SOLUCIONADO 😉
Hola Enrique, fenomenal tu artículo, con una gran explicación.
Tengo una duda a la hora de consumir un servicio (un GET por ejemplo), ya que aparentemente funciona, pero en el momento en el que Angular 2 demanda el servicio y obtiene la respuesta, el error de consola es:
XMLHttpRequest cannot load http://10.42.100.14:8082/test. No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
http://10.42.100.14:8082/test es un servicio levantado en una máquina virtual propia que devuelve un JSON de prueba.
¿Sabes cómo se soluciona este problema?
Muchas gracias y un saludo.
Hola Jesús,
Es un problema de CORS. Estas intentando acceder a un servidor desde un dominio distinto (imagino que localhost). O bien permites el acceso desde el servidor (en caso de que lo necesites hacer así en producción), o bien utilizas un proxy para simular que es el mismo dominio (es la mejor opción para desarrollo. Mírate el npm corsproxy).
Saludos
Muchas gracias Enrique.
Al final, y debido a que es un problema ajeno a Angular 2, he permitido el acceso desde el servidor (servicios REST realizados con SpringBoot, con lo que simplemente inyectar la etiqueta @CrossOrigin en los controladores ya resuelve el problema)
De todas formas, gran aporte de npm corsproxy, lo tendré en cuenta.
Un saludo.
Muy bueno el articulo te queria consultar como typescript es un superset de javascript cualquier codigo que escribimos en javascript es entendido cuando estructuramos al objeto observable o al observador esto porque RX tiene soporte para javascript pero no para TYPESCRIPT y en los ejemplos que encuentro en internet la mayoria estan en javascript para RX
Sabes si hay ejemplos de codigo directamente en typescript porque con angular-cli escribo en typescript y luego lo transpila pero la inversa hay alguna herramiento para convertir javascript en typescript asi con las guias de rx con java script puedo ver como quedan en typescript para poder usar genericos etc como si muestran con el soporte de C# para RX
gracias
TypeScript efectivamente es capaz de entender código JS «antiguo» (léase ES5), así que no deberías tener problemas en la integración. No conozco ninguna herramienta para compilar JS a TS, y me parece difícil que no encuentres, lo normal es hacer el proceso a la inversa 🙁
Hola Enrique, gracias por el artículo, ha servido de mucha utilidad. Tengo una pregunta: pones que para importar la librería hay que importar ‘import { Http } from ‘@angular/http’;’, sin embargo, en otro tutorial que seguí dice de importar ‘import {Http} from «angular2/http»;’. El primer caso sí me funciona, el segundo no. ¿Podrías decirme, si lo sabes, por favor, qué diferencia hay entre ellos?
Muchas gracias.
Sería un artículo más antiguo. Angular 2 es muy reciente y en el camino hacia la versión estable han ido modificando la nomenclatura de sus librerías, de ahí la diferencia.
Hola Enrique, tu artículo es fantástico, me ha servido para entender los observables.
Una preguntilla desde mi ignorancia, en «productsService.ts» tienes «get products()»; ¿no sería «getProducts()»? Y otra cosa, en «myApp.ts» tienes «productsService.products.subscribe(…» No entiendo bien cómo desde «myApp.ts» puedes ver la propiedad «products»; al importar «ProductsService» se supone que sólo tendría acceso al getter ¿no?
Un saludo y enhorabuena de nuevo por el artículo 🙂
Hola Luis,
Gracias, celebro que te haya gustado el artículo. No es un error, el código está bien. Ambas preguntas están relacionadas con los «Accessors» de TypeScript.
TypeScript te permite definir getters/setters como mecanismo para interceptar accesos a datos miembro del objeto. De este modo, cuando defines el getter
get products(){...};
, lo que consigues es que si alguien usa el dato miembro products de tu clase **para leer su contenido**, en realidad esté llamando a esta función.Esto normalmente se utiliza para hacer validaciones en los setters y limitar así la capacidad de modificación de una variable. En este ejemplo, en todo caso, voy por otro lado. Si te fijas el dato miembro products no existe. Al crear el accesor
get products()
, es como si estubiera creando un dato miembro virtual «products», que en realidad devuelve el contenido de la función.Hola Enrique, gracias a ti por responder. Duda resuelta 🙂 Un saludo
Hola Enrique, gracias por la explicación, después de haber estado peleándome con los observables, finalmente los he llegado a entender bien gracias a tu artículo :). Quería preguntarte, porque hay una cosa que no veo clara y es donde está definida la propiedad this.products. Veo que cuando te subscribes al observable, devuelves el resultado en this.products, pero como digo, no lo veo declarado en ningún sitio. Gracias de antemano y un saludo!
Justamente tienes esa duda resuelta en la respuesta al comentario de José Sánchez.
¡Saludos!
Cierto! disculpa Enrique, no leí ese comentario :S, gracias por tu rápida respuesta!
hola Enrique muy bueno el artículo. Una consulta: si la ruta de mi api consulta una base de datos, y los datos involucrados en el observable han cambiado en la bd, como se entera el observable sin volver a ejecutar el get ? si no se entera, me podrías orientar en qué harías tu para actualizar ese observable periódicamente? gracias Pablo
El observable en una API REST desde el lado de cliente, solo te sirve para eso, para el cliente. Es decir, no te vale para saber que el dato se ha actualizado en servidor.
Normalmente para suscribirte a cambios en el servidor se utilizan websockets en lugar de una API REST. Es lo que hace firebase, por ejemplo.
Otra opción más casera sería o bien que el servidor te avisara por push notification de que ha habido un cambio, o bien que el cliente vaya haciendo polling cada cierto tiempo para comprobar si han habido cambios.
Saludos
Hola Enrique, fantástico articulo enhorabuena, pero me ha surgido una duda con las librerías que añado más abajo. Con la ultima version de Angular 2 o Angular CLI es necesario para poder interactuar con el Api?
Gracias
node_modules/angular2/bundles/angular2-polyfills
node_modules/systemjs/dist/system.src
node_modules/rxjs/bundles/Rx
node_modules/angular2/bundles/angular2.dev
code.angularjs.org/2.0.0-beta.1/http
Tomo nota de este ejemplo, ya que estoy empezando a modificar la web de WordPress a Angular 2
Gracias por la información realmente he estado buscando información sobre los Observables y entre tanto código que he visto no entiendo porque un simple subscribe hace que se convierta en observable. he encontrado ejemplos donde se utilizan las siguientes instrucciones:
import { Observable } from ‘rxjs/rx’;
import { BehaviorSubject } from ‘rxjs/BehaviorSubject’;
import ‘rxjs/add/operator/map’;
todos: Observable
private _todos: BehaviorSubject;
this._todos = <BehaviorSubject>new BehaviorSubject([]);
this.todos = this._todos.asObservable();
this.http.get(this.baseUrl)
.map(response => response.json())
.subscribe(data => {
this.dataStore.todos = data;
this._todos.next(Object.assign({}, this.dataStore).todos);
},
error => console.log(‘Could not load todos.’));
https://coryrylan.com/blog/angular-observable-data-services
La duda que necesito resolver es supongamos que al entrar a la aplicación la primer llamada al API obtiene el catalogo de productos y estos se despliegan en una simple lista. Si en dado caso en la base de datos se agrega un nuevo producto la única manera de que la lista refleje este nuevo producto es que cada cierto tiempo la aplicación haga una nueva petición al API y esta le devuelva la info actualizada. Como veo en el ejemplo que colocas y en el link que encontré que adjunte arriba nunca hay un llamado nuevamente al API por lo que como se actualizara la información si no hay esa petición al API.
En el link que adjunte veo que al momento de actualizar un dato hacen una petición al API y luego this.dataStore.todos.push(data);
this._todos.next(Object.assign({}, this.dataStore).todos);
lo que en efecto actualiza la vista. Pero que pasa si nunca hay un evento, ese producto se agrega a la base de datos pero si nadie le da refrescar a la vista nunca se vería ese nuevo producto.
Es mas si 10 personas tenemos abierta la misma aplicación y viendo la misma vista que pasaría si yo le pongo un comentario a un producto como las 9 personas restantes verían ese comentario si nunca refrescan la vista.
Lo unico que se me ocurre es poner un timer y decirle que cada 10 segundos llame a la función que invoca el API para esto ocasionaría una sobre carga de la API al estar recibiendo esa cantidad de peticiones?
Te agradeceria si puedes ayudarme con esto, realmente estoy hecho un lío con respecto a este tema.
Hola Enrique, buen día, primeramente te felicito por artículo que me ha sacado de dudas después de semanas.
Tengo un pequeño problema y espero puedas ayudarme a pesar del tiempo que ha pasado desde este post, no tengo problemas ni errores en el código, pero al momento de correr mi «ionic serve» me ocurre un runtime error, y me dice «Can’t resolve all parameters for (la clase donde te suscribes a tu productService.ts, que sería tu app.ts) y me marca un error en el parametro del constructor, donde tu instancias tu clase de productService: productService »
Tienes idea de como puedo resolver este problema? de antemo gracias y felicidades nuevamente. 🙂
Así a priori parece que te falta declarar productService como provider, en el NgModule principal (o bien como provider del componente).
Se agradece, gran aporte! Espero que sigas publicando este tipo de contenidos.