Tutorial Angular – 8. HTTP

      2 comentarios en Tutorial Angular – 8. HTTP

HTTP


En este tutorial añadiremos las siguientes características sobre persistencia de datos, con la ayuda de HttpClient de Angular.

  • HeroService obtendrá datos mediante peticiones HTTP.
  • Los usuarios podrán añadir, editar y borrar héroes y guardar estos cambios por HTTP.
  • Los usuarios podrán buscar héroes por nombre.

Cuando hayas terminado con esta página, la aplicación debería parecerse a este ejemplo / descarga ejemplo

Habilitar servicios HHTP

HttpClient es el mecanismo de Angular para comunicarse con un servidor remoto a través de HTTP.
Para hacer que HttpClient esté disponible en cualquier punto de la aplicación:

  • abre el AppModule raíz.
  • importa el símbolo HttpClientModule de @angular/common/http
  • añádelo al array @NgModule.imports.

Simular un servidor de datos

Este tutorial imita la comunicación con un servidor de datos remoto usando el módulo In-memory web API.
Después de instalar el módulo, la aplicación hará peticiones y recibirá respuestas de HttpClient sin saber que In-memory web API intercepta estas peticiones, aplicándolas a un almacén de datos en memoria (In-memory) y devolviendo respuestas simuladas.
Esta funcionalidad es muy práctica para este tutorial. No tendremos que configurar un servidor para aprender acerca de HttpClient.
También puede ser de utilidad en las fases iniciales de desarrollo de tu propia aplicación, cuando la API del servidor no está aún correctamente definida o no implementada

Importate: el módulo In-memory Web API no guarda ninguna relación con HTTP de Angular.

Ai sólo estás leyendo este tutorial para aprender acerca de HttpClient, puedes saltarte este paso. Si estás escribiendo el código, sigue las instrucciones a continuación para añadir In-memory Web API.

Desde npm, instala In-memory Web API.

Importa InMemoryWebApiModule y InMemoryWebApiModule, que crearemos a continuación.

Añade InMemoryWebApiModule al array @NgModule.importsdespués de importar HttpClient, – mientras lo configuras con InMemoryDataService,

El método de configuración forRoot() usa una clase InMemoryDataService que hace que la base de datos en memoria este disponible.
El ejemplo de Tour de Héroes crea esta clase src/app/in-memory-data.service.ts con el siguiente contenido:

Este fichero reemplaza mock-heroes.ts, que ya podemos borrar.
Cuando el servidor esté listo, podemos desactivar In-memory Web API y las peticiones de la aplicación irán contra el servidor.
Ahora volvemos con HttpClient.

Héroes y HTTP

Importamos algunos símbolos HTTP que vamos a necesitar.

Inyecta HttpClient en el constructor usando una propiedad privada llamada http.

Sigue inyectado MessageService. Lo llamaremos tan a menudo que es conveniente tenerlo en un método privado llamado log.

Define HeroesUrl con la dirección del servidor que apunta a los héroes.

Obtener los héroes con HttpCLient
Actualmente HeroService.getHeroes() usa la función of() de RxJS para devolver un array de héroes simulados como un Observable<Hero[]>.

Modifica este método para usar HttpClient.

Actualiza el navegador. Los datos del héroe debería cargarse correctamente del servidor simulado.
Hemos intercambiado of por http.get y la aplicación sigue funcionando sin ningún otro cambio porque ambos métodos devuelven un Observable<Hero[]>.

Los métodos Http devuelven un valor

Todos los métodos HttpClient devuelven un Observable de RxJS de algo.
HTTP es un protocolo de petición/respuesta. Hacemos una petición y devuelve una única respuesta.
En general, un Observable puede devolver múltiples valores. Un Observable de HttpClient siempre emite un único valor y finaliza, no vuelve a emitir.
Esta llamada en particular devuelve un Observable<Hero[]>, literalmente un observable de array de héroes. En la práctica, sólo devolverá un único array de héroes.

HttpClient.get devuelve los datos de respuesta

HttpClient.get devuelve por defecto el cuerpo de la respuesta como un objeto JSON sin tipo. Aplicando el indicador de tipo <Hero>, nos devuelve un objeto tipado.
La forma del JSON viene definida por el API del servidor de datos. El API de Tour de Héroes devuelve los datos como un array.

Otras APIs pueden ‘enterrar’ los datos necesarios dentro de un objeto. Es posible que tengamos que ‘cavar’ en esos datos procesando el Observable de respuesta con el operador map de RxJS.

Manejo de errores

Las cosas pueden fallar, especialmente cuando se obtienen datos de un servidor remoto. El método HeroService.getHeroes() capturar posibles errores y gestionarlos adecuadamente.
Para capturar el error, haremos «pipe» sobre el observable resultante de http.get(), mediante el operador de RxJS catchError().
Importa el símbolo catchError() de rxjs/operators, junto con otros operadores que necesitaremos más adelante.

Ahora, extiende el resultado del observable con el método .pipe() y dale el operador catchError().

El operador catchError() intercepta un Observable que haya fallado. Le pasa el error a un manejador de errores que puede hacer lo que considere con el error.
El siguiente método handleError() informa del error y devuelve un resultado inocuo, de forma que la aplicación sigue funcionando.

handleError

El siguiente errorHandler estará compartido por varios métodos de HeroService así que debería cubrir diferentes necesidades.
En lugar de manejar el error directamente, devuelve una función manejadora de errores a catchError que está configurada con el nombre de la operación fallida y un valor de retorno seguro.

Después de reportar el error a la consola, el manejador construye un mensaje coherente para el usuario final y devuelve un valor seguro a la aplicación para que siga funcionando,
Como cada método del servicio devuelve un tipo distinto de Observable, errorHandler() toma un parámetro ‘tipo’, de modo que pueda devolver un valor del tipo que la aplicación espera.

Interceptar el Observable

Los métodos de HeroService interceptarán el flujo de los valores del observable y enviarán un mensaje (vía log) al área de mensajes el final de la página.
Esto se hará con el operador de RxJS tap, el cual mira el valor de los observables, hace algo con estos valores y los pasa. La llamada de vuelta de tap no modifica propiamente los valores.
Aquí está la versión final de getHeroes con el tap que registra (log) la operación.

Obtener el héroe por id

La mayoría de APIs soportan una petición tipo obtener por id con el formato api/hero/:id (como api/hero/11). Añade un método HeroService.getHero() para hacer esa petición:

Hay tres diferencias significativas con getHeroes().

  • construye una URL de petición con el id del héroe solicitado.
  • el servidor debería responder con un único héroe en lugar de un array de héroes.
  • por tanto, getHero devuelve un Observable<Hero< (un observable de objetos Héroe) en lugar de un observable de arrays de héroes.

Actualizar héroes

Editando el nombre de un héroe en la vista del detalle del héroe. A medida que tecleas, el nombre del héroe se actualiza en la parte de arriba de la página. Pero cuando haces clic en el botón ‘volver’, los cambios se pierden.
Si quieres persistir los cambios, debes escribirlos en el servidor.
Al final de la plantilla de detalle de héroes, añade un botón de guardado con un evento click vinculado que invoca un nuevo método del componente llamado save().

Añade el siguiente método save(), el cual persiste los cambios en el nombre del héroe usando el método updateHero() del servicio del héroe y navega de vuelta a la vista anterior.

Añadir HeroService.updateHero()

La estructura general del método updateHero() es similar a la de getHeroes(), pero usa http.put para persistir en el servidor el héroe modificado.

El método HttpClient.put() toma tres parámetros

  • la URL.
  • los datos a actualizar (el héroe modificado en este caso).
  • opciones

La URL no ha cambiado. La API web de héroes conoce cual es el héroe a actualizar observando el id del héroe.
La API web de héroes espera una cabecera HTTP concreta en las peticiones de guardado. Esa cabecera estña en la constante httpOptions definida en HeroService.

Actualiza el navegador, cambia un nombre de héroe, guarda el cambio y haz clic en el botón ‘volver’. El héroe aparece ahora en la lista con el nombre modificado.

Añadir un nuevo héroe

Para añadir un héroe, esta aplicación sólo necesita el nombre del héroe. Podemos usar un elemento <input> con un botón de ‘añadir’.
Inserta lo siguiente en la plantilla HeroesComponent, justo después de la cabecera:

En respuesta al evento clic, llamamos al manejador del clic del componente y después vaciamos el campo de entrada, de modo que esté listo para otro nombre.

Cuando el nombre no está vacío, el manejador crea un objeto Hero a partir del nombre (tan sólo le falta el id) y se lo pasa al método addHero() del servicio.
Cuando addHero guarda con éxito, la retrollamada subscribe recibe el nuevo héroe y lo inserta en la lista de heroes a mostrar.
Escribiremos HeroService.addHero() en la siguiente sección.

Añadir HeroService.addHero()

Añade el siguiente método addHero() a la clase HeroService

HeroService.addHero() difiere de updateHero de dos maneras:

  • llama a HttpClient.post() en lugar de put()
  • espera a que el servidor genere un id para el nuevo héroe, el cual devuelve en Observable<Hero> al invocador.

Actualiza el navegador y añade algunos héroes.

Borrar un héroe

Cada héroe en el listado de héroes debería tener un botón de borrado.
Añade el siguiente botón a la plantilla HeroesComponent, después del nombre del héroe en el elemento <li>.

El HTML para el listado de héroes debería quedar así:

Para posicionar el botón de borrado a la derecha de la caja el héroe, añade CSS a heroes.component.css. Encontrarás este CSS en la revisión final de código.
Añade el manejador delete() al componente.

Aunque el componente delega el borrado del héroe en HeroService, sigue siendo responsable de actualizar su propio listado de héroes. El método delete() del componente borra inmediatamente el héroe a borrar de la lista, anticipando que HeroService.
En realidad, no hay nada que el componente deba hacer con el Observable devuelto por heroService.delete(). Debe suscribirse de todas maneras.

Si dejáramos de usar susbscribe(), el servicio no enviaría la petición de borrado al servidor. Como norma general, un Observable no hace nada hasta que alguien se suscribe. Comprueba esto por ti mismo eliminando temporalmente subscribe(), haciendo clic en «Dashboard» (Cuadro de Mandos) y después haciendo clic en «Heroes». Verás la lista de héroes completa de nuevo.

Añadir HeroService.deleteHero()

Añade un método deleteHero() a HeroService así:

Fíjate que

  • llama a HttpClient.delete.
  • la URL es la URL del recurso de héroes más el id del héroe a eliminar.
  • no se envían datos como hicimos con put y post.
  • seguimos enviando httpOptions.

Actualiza el navegador y prueba la nueva funcionalidad de borrado.

Búsqueda por nombre

En el ejercicio anterior, hemos aprendido a encadenar operadores Observable juntos de forma que minimizamos el número de peticiones HTTP similares y consumimos menos ancho de banda.
Vamos a añadir una opción de búsqueda de héroes al Cuadro de Mandos. Mientras el usuario teclea un nombre en el campo de búsqueda, haremos peticiones HTTP repetidamente por héroe filtrando por ese nombre. El objetivo es hacer el mínimo de peticiones necesarias.

HeroService.searchHeroes

Empezamos añadiendo un método searchHeroes a HeroService.

El método devuelve inmediatamente un array vacío si no hay un término a buscar. El resto se parece bastante a getHeroes(). LA única diferencia significativa es la URL, que incluye una cadena de consulta con el termino a buscar.

Añadir búsqueda al Cuadro de Mandos
Abre la plantilla de DashboardComponent y añade el elemento de búsqueda <app-hero-search> al final de dicha plantilla.

Esta plantilla se parece mucho al iterador *ngFor en la plantilla HeroesComponent.
Desafortunadamente, añadir este elemento rompe la aplicación. Angular no puede encontrar un selector que coincida con <app-hero-search>.
El componente HeroSearchComponent aún no existe. Arreglaremos eso.

Crear HeroSearchComponent

Creamos un HeroSearchComponent con el CLI.

El CLI genera los tres ficheros de HeroSearchComponent y añade el componente en las declaraciones de AppModule.
Reemplaza la plantilla de HeroSearchComponent generada por un cuadro de texto y un listado de coincidencias así.

Añade unos estilos CSS privados a hero-search.component.css como verás en la revisión final de código.
A medida que el usuario teclea en el cuadro de texto, un víncuño al evento keyup (soltar tecla) llama al método search() del componente con el nuevo valor del cuadro de texto.

AsyncPipe

Como esperabamos, *ngFor repite el objeto héroe.
Mira detenidamente y verás que *ngFor itera sobre una lista llamada heroes$, no heroes.

El $ es una convención que indica que heroes$ es un Observable, no un array.
*ngFor no puede hacer nada con un Observable. Pero hay también un carácter ‘tubería’ (pipe |), seguido por async, que identifica a AsyncPipe de Angular.
AsyncPipe suscribe automáticamente a un Observable de modo que ya no tenemos que hacerlo en la clase del componente.

Arregla la clase HeroSearchComponent

Reemplaza la clase generada HeroSearchComponent y sus metadatos de la siguiente manera.

Fíjate en que la declaración de heroes$ es un Observable.

Lo ubicaremos en ngOnInit(). Pero antes de hacerlo, nos fijaremos en la definición de searchTerms

El sujeto de RxJS searchTerms

La propiedad searchTerms se declara como un Subject (sujeto) de RxJS.

Un Subject es a la vez un origen de valores observables y un Observable. Podemos suscribirnos a un Subject como lo haríamos con cualquier Observable.
Podemos también insertar valores en el Observable llamando a su método next(valor) del mismo modo que search().
El método search() se llama a través de una vinculación de eventos al evento keystroke (pulsar tecla) de la caja de texto.

Cada vez que el usuario teclea en la caja de texto, la vinculación llama a search() con el valor de la caja de texto, un «término de búsqueda» (search term). El searchTerms se convierte en un Observable emitiendo una ‘corriente’ (stream) de términos de búsqueda.

Encadenando operadores RxJS

Pasar un nuevo término de búsqueda directamente a searchHeroes() después de cada tecleo crearía una cantidad excesiva de peticiones HTTP, saturando los recursos del servidor y consumiendo los datos de nuestra tarifa móvil.
En su lugar, el método ngOnInit() canaliza el observable searchTerms a través de una secuencia de operadores RxJS que reduce el número de llamadas al searchHeroes(), devolviendo finalmente un observable de los resultados de la búsqueda de héroes (cada Hero[]).
Aquí está el código.

  • debounceTime(300) realiza una pausa de 300 milisegundos antes de pasar la última cadena. Nunca se harán peticiones con una frecuencia mayor a 300ms.
  • distinctUntilChanged se asegura de que la petición se envíe sólo si el texto de filtrado ha cambiado
  • switchMap() llama al servicio de búsqueda por cada término de búsqueda que llega hasta debounce y distinctUntilChanged. Cancela y descarta los observables de búsqueda anteriores, devolviendo sólo el último.

Con el operador switchMap, cada evento de teclado definido puede lanzar una llamada al método HttpClient.get(). Incluso con una pausa de 300ms entre peticiones, puedes tener múltiples peticiones HTTP activas y pueden no devolverse en el orden enviado.

switchMap() preserva el orden original de las peticiones y sólo devolverá el observable de la llamada a la petición HTTP más reciente. Los resultados de las llamadas anteriores son cancelados y descartados.

Ten en cuenta que cancelar un Observable searchHeroes() previo en realidad no aborta una petición HTTP pendiente. Los resultados no deseados son simplemente descartados antes de llegar al código de la aplicación.

Recuerda que la clase del componente no se suscribe al observable heroe$. Ese es el trabajo de AsyncPipe en la plantilla.

Pruébalo

Ejecuta la aplicación de nuevo. En el Cuadro de Mandos, introduce algún texto en la caja de texto. Si introduces caracteres

Revisión final de código

Tu aplicación debería parecerse a este ejemplo en vivo / descarga ejemplo
Aquí están los ficheros tratados en esta página (todos ellos en la carpeta src/app).

HeroService, InMemoryDataService y AppModule

HeroesComponent

HeroDetailComponent

HeroSearchComponent

Resumen

Estamos al final de este viaje y hemos conseguido muchas cosas.

  • Hemos añadido las dependencias necesarias para usar HTTP en la aplicación.
  • Hemos refactorizado HeroService para cargar héroes desde una API Web.
  • Hemos extendido HeroService para que permita los métodos post(), put() y delete().
  • Hemos actualizado los componentes para permitir que editen, añadan y borren héroes.
  • Hemos configurado una API Web en memoria.
  • Hemos aprendido como usar Observables.

Esto finaliza el tutorial ‘Tour de Héroes’. Ahora estamos listos para aprender más acerca de desarrollo en Angular en la sección de Fundamentos, empezando por la guía de Arquitectura.

Nota: puedes encontrar el documento original de esta entrada en https://angular.io/tutorial/toh-pt6

2 pensamientos en “Tutorial Angular – 8. HTTP

  1. Alejandro del Rey

    Esta muy bueno, aunque tiene un pequeno error …. en la vista de heroes, cuando eliminas todos los heroes, y despues quieres adicionar uno, lo adiciona sin id por lo cual da error….

    me estuve fijando en el codigo original de la pagina oficial y encontre este codigo que se encarga de eso….

    genId(heroes: Hero[]): number {
    return heroes.length > 0 ? Math.max(…heroes.map(hero => hero.id)) + 1 : 11;
    }

    Hay que ponerlo en in-memory-data.service.ts …..

    Bueno Muchas gracias por todo. ahora mismo estoy en el desarrollo del buscador. Cuando finalize les hago otro comentario.

    Responder

Responder a CAR Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *