26 KiB
Obtener datos desde un servidor
En este tutorial, agregará las siguientes características de persistencia de datos con la ayuda de
Angular HttpClient
.
- El
HeroService
obtiene datos del héroe con solicitudes HTTP. - Los usuarios pueden agregar, editar y eliminar héroes y guardar estos cambios a través de HTTP.
- Los usuarios pueden buscar héroes por nombre.
Para ver la aplicación de ejemplo que describe esta página, consulte el.
Habilitar servicios HTTP
HttpClient
es el mecanismo de Angular para comunicarse con un servidor remoto a través de HTTP.
Haga que HttpClient
esté disponible en todas partes de la aplicación en dos pasos. Primero, agréguelo a la raíz AppModule
importándolo:
A continuación, aún en el AppModule
, agregue HttpClient
a el arreglo imports
:
Simular un servidor de datos
Este ejemplo de tutorial imita la comunicación con un servidor de datos remoto mediante el uso de el modulo API web en memoria.
Después de instalar el módulo, la aplicación realizará solicitudes y recibirá respuestas del HttpClient
sin saber que la API web en memoria está interceptando esas solicitudes,
aplicándolos a un almacén de datos en memoria y devolviendo respuestas simuladas.
Al utilizar la API web en memoria, no tendrá que configurar un servidor para obtener información sobre HttpClient
.
Importante: el módulo API web en memoria no tiene nada que ver con HTTP en Angular.
Si solo está leyendo este tutorial para aprender sobre HttpClient
, puede omitir este paso.
Si está codificando junto con este tutorial, quédese aquí y agregue la API web en memoria ahora.
Instale el paquete de API web en memoria desde npm con el siguiente comando:
npm install angular-in-memory-web-api --saveEn el AppModule
, importe el HttpClientInMemoryWebApiModule
y la clase InMemoryDataService
,
que crearás en un momento.
Después del HttpClientModule
, agregue el HttpClientInMemoryWebApiModule
a el arreglo de AppModule
justo en imports
y configúrelo con el InMemoryDataService
.
El método de configuración forRoot()
toma una clase InMemoryDataService
eso prepara la base de datos en memoria.
Genere la clase src/app/in-memory-data.service.ts
con el siguiente comando:
Reemplace el contenido predeterminado de in-memory-data.service.ts
con lo siguiente:
El archivo in-memory-data.service.ts
asumirá la función de mock-heroes.ts
.
Sin embargo, no elimine mock-heroes.ts
todavía, ya que aún lo necesita para algunos pasos más de este tutorial.
Cuando el servidor esté listo, desconectará la API web en memoria y las solicitudes de la aplicación se enviarán al servidor.
{@a import-heroes}
Heroes y HTTP
En el HeroService
, importe HttpClient
y HttpHeaders
:
Aún en el HeroService
, inyecte HttpClient
en el constructor en una propiedad privada llamada http
.
Observe que sigue inyectando el MessageService
pero como lo llamará con tanta frecuencia, envuélvalo en un método privado log()
:
Defina el heroesUrl
del formulario :base/:collectionName
con la dirección del recurso heroes en el servidor.
Aquí base
es el recurso al que se hacen las solicitudes,
y collectionName
es el objeto de datos de héroes en in-memory-data-service.ts
.
Consigue héroes con HttpClient
El actual HeroService.getHeroes()
usa la función RxJS of()
para devolver una serie de héroes simulados
como un Observable<Hero[]>
.
Convierta ese método para usar HttpClient
de la siguiente manera:
Actualiza el navegador. Los datos del héroe deben cargarse correctamente desde el servidor simulado.
Ha cambiado of()
por http.get()
y la aplicación sigue funcionando sin ningún otro cambio
porque ambas funciones devuelven un Observable <Hero[]>
.
Los métodos HttpClient
devuelven un valor
Todos los métodos HttpClient
devuelven un RxJS Observable
de algo.
HTTP es un protocolo de solicitud/respuesta. Realiza una solicitud, devuelve una sola respuesta.
En general, un observable puede devolver múltiples valores a lo largo del tiempo.
Un observable de HttpClient
siempre emite un único valor y luego se completa, para nunca volver a emitir.
Esta llamada particular a HttpClient.get()
devuelve un Observable<Hero[]>
; es decir, "un observable de un arreglo de héroes". En la práctica, solo devolverá un único conjunto de héroes.
HttpClient.get()
devuelve datos de respuesta
HttpClient.get()
devuelve el cuerpo de la respuesta como un objeto JSON sin tipo de forma predeterminada.
Al aplicar el especificador de tipo opcional, <Hero[]>
, se agregan capacidades de TypeScript, que reducen los errores durante el tiempo de compilación.
La API de datos del servidor determina la forma de los datos JSON. La API de datos Tour of Heroes devuelve los datos del héroe como una matriz.
Otras API pueden enterrar los datos que desea dentro de un objeto.
Puede que tenga que desenterrar esos datos procesando el resultado Observable
con el operador RxJS map()
.
Aunque no se trata aquí, hay un ejemplo de map()
en getHeroNo404()
método incluido en el código fuente de muestra.
Manejo de errores
Las cosas salen mal, especialmente cuando obtiene datos de un servidor remoto.
El método HeroService.getHeroes()
debería detectar errores y hacer algo apropiado.
Para detectar errores, "filtra" el resultado observable desde http.get()
a través de un operador RxJS catchError()
.
Importe el símbolo catchError
desde rxjs/operadores
, junto con algunos otros operadores que necesitará más adelante.
Ahora extienda el resultado observable con el método pipe()
y
darle un operador catchError()
.
El operador catchError()
intercepta un Observable
que falló.
Pasa el error a un controlador de errores que puede hacer lo que quiera con el error.
El siguiente método handleError()
informa el error y luego devuelve un
resultado inocuo para que la aplicación siga funcionando.
handleError
El siguiente handleError()
será compartido por muchos métodos HeroService
así que está generalizado para satisfacer sus diferentes necesidades.
En lugar de manejar el error directamente, devuelve una función de controlador de errores a catchError
que
se configuró con el nombre de la operación que falló y un valor de retorno seguro.
Después de informar el error a la consola, el controlador construye un mensaje fácil de usar y devuelve un valor seguro a la aplicación para que la aplicación pueda seguir funcionando.
Como cada método de servicio devuelve un tipo diferente de resultado 'Observable',
handleError()
toma un parámetro de tipo para que pueda devolver el valor seguro como el tipo que la aplicación espera.
Tap en el Observable
Los métodos HeroService
aprovecharán el flujo de valores observables
y envíe un mensaje, a través del método log()
, al área de mensajes en la parte inferior de la página.
Lo harán con el operador RxJS tap()
,
que mira los valores observables, hace algo con esos valores,
y los pasa
La devolución de llamada tap()
no toca los valores en sí mismos.
Aquí está la versión final de getHeroes()
con el tap()
que registra la operación.
Obtener héroe por id
La mayoría de las API web admiten una solicitud get by id en la forma : baseURL /: id
.
Aquí, la base URL es el heroesURL
definido en la Heroes y HTTP sección (api/heroes
) y id es
El número del héroe que quieres recuperar. Por ejemplo, api/heroes/11
.
Actualice el método HeroService
getHero()
con lo siguiente para hacer esa solicitud:
Hay tres diferencias significativas de getHeroes()
:
getHero()
construye una URL de solicitud con la identificación del héroe deseado.- El servidor debe responder con un solo héroe en lugar de una serie de héroes.
getHero()
devuelve unObservable<Hero>
("un observable de objetos Hero") en lugar de un observable de arreglos de héroes.
Actualizar héroes
Edite el nombre de un héroe en la vista de detalles del héroe. A medida que escribe, el nombre del héroe actualiza el encabezado en la parte superior de la página. Pero cuando hace clic en el "botón volver", los cambios se pierden.
Si desea que los cambios persistan, debe volver a escribirlos en el servidor.
Al final de la plantilla de detalles del héroe, agregue un botón de guardar con un evento de "clic"
enlace que invoca un nuevo método de componente llamado save()
.
En la clase de componente HeroDetail
, agregue el siguiente método save()
, que persiste los cambios de nombre de héroe usando el servicio de héroe
updateHero()
y luego navega de regreso a la vista anterior.
Agregar HeroService.updateHero()
La estructura general del método updateHero()
es similar a la de
getHeroes()
, pero usa http.put()
para persistir el héroe cambiado
en el servidor Agregue lo siguiente al HeroService
.
El método HttpClient.put()
toma tres parámetros:
- la URL
- los datos para actualizar (el héroe modificado en este caso)
- opciones
La URL no se modifica. La API web de héroes sabe qué héroe actualizar al mirar el "id" del héroe.
La API web de héroes espera un encabezado especial en las solicitudes de guardado HTTP.
Ese encabezado está en la constante httpOptions
definida en el HeroService
. Agregue lo siguiente a la clase HeroService
.
Actualiza el navegador, cambia el nombre de un héroe y guarda tu cambio. El save()
El método en HeroDetailComponent
navega a la vista anterior.
El héroe ahora aparece en la lista con el nombre cambiado.
Agrega un nuevo héroe
Para agregar un héroe, esta aplicación solo necesita el nombre del héroe. Puede utilizar un <input>
elemento emparejado con un botón Agregar.
Inserte lo siguiente en la plantilla HeroesComponent
, justo después
El encabezado:
En respuesta a un evento de clic, llame al controlador de clic del componente, add()
, y luego
borre el campo de entrada para que esté listo para otro nombre. Agregue lo siguiente al
Clase Componente de héroes
:
Cuando el nombre de pila no está en blanco, el controlador crea un objeto similar a un "Héroe"
del nombre (sólo falta el id
) y lo pasa al método addHero()
del servicio.
Cuando addHero()
se guarda correctamente, la devolución de llamada subscribe()
recibe el nuevo héroe y lo empuja a la lista de "héroes" para mostrarlo.
Agregue el siguiente método addHero()
a la clase HeroService
.
addHero()
difiere de updateHero()
en dos formas:
- Llama a
HttpClient.post()
en lugar de aput()
. - Espera que el servidor genere una identificación para el nuevo héroe,
que devuelve en el
Observable<Hero>
a la persona que llama.
Actualiza el navegador y agrega algunos héroes.
Eliminar un héroe
Cada héroe de la lista de héroes debe tener un botón de eliminación.
Agregue el siguiente elemento de botón a la plantilla HeroesComponent
, después del héroe
nombre en el elemento repetido <li>
.
El HTML de la lista de héroes debería verse así:
Para colocar el botón de eliminar en el extremo derecho de la entrada del héroe,
agregue algo de CSS al heroes.component.css
. Encontrarás ese CSS
en el código de revisión final a continuación.
Agregue el controlador delete()
a la clase del componente.
Aunque el componente delega la eliminación de héroes al HeroService
,
sigue siendo responsable de actualizar su propia lista de héroes.
El método delete()
del componente elimina inmediatamente el hero-to-delete de esa lista,
anticipando que el HeroService
tendrá éxito en el servidor.
Realmente no hay nada que ver el componente con el "Observable" devuelto por
heroService.delete()
pero debe suscribirse de todos modos.
Si se olvida de subscribe()
, el servicio no enviará la solicitud de eliminación al servidor.
Como regla general, un "Observable" no hace nada hasta que algo se suscribe.
Confirme esto por sí mismo eliminando temporalmente el subscribe()
,
haciendo clic en "Panel de control", luego en "Héroes".
Verás la lista completa de héroes nuevamente.
A continuación, agregue un método deleteHero()
a HeroService
como este.
Tenga en cuenta los siguientes puntos clave:
deleteHero()
llama aHttpClient.delete()
.- La URL es la URL del recurso de héroes más el "id" del héroe a eliminar.
- No envías datos como lo hiciste con
put()
ypost()
. - Aún envías las
httpOptions
.
Actualice el navegador y pruebe la nueva función de eliminación.
Buscar por nombre
En este último ejercicio, aprenderá a encadenar operadores "observables" para que pueda minimizar la cantidad de solicitudes HTTP similares y consumir ancho de banda de la red de forma económica.
Agregará una función de búsqueda de héroes al Tablero. A medida que el usuario escribe un nombre en un cuadro de búsqueda, harás solicitudes HTTP repetidas para héroes filtrados por ese nombre. Su objetivo es emitir solo tantas solicitudes como sea necesario.
HeroService.searchHeroes()
Comience agregando un método searchHeroes()
al HeroService
.
El método regresa inmediatamente con una matriz vacía si no hay un término de búsqueda. El resto se parece mucho a "getHeroes()", siendo la única diferencia significativa la URL, que incluye una cadena de consulta con el término de búsqueda.
Agregar búsqueda al panel
Abra la plantilla DashboardComponent
y
agregue el elemento de búsqueda de héroe, <app-hero-search>
, al final del marcado.
Esta plantilla se parece mucho al repetidor *ngFor
en la plantilla HeroesComponent
.
Para que esto funcione, el siguiente paso es agregar un componente con un selector que coincida con <app-hero-search>
.
Crear HeroSearchComponent
Cree un "HeroSearchComponent" con El Cli.
ng generate component hero-searchEl Cli genera los tres archivos de HeroSearchComponent
y agrega el componente a las declaraciones en AppModule
.
Reemplace la plantilla HeroSearchComponent
generada con un <input>
y una lista de resultados de búsqueda coincidentes, de la siguiente manera.
Agregue estilos CSS privados a hero-search.component.css
como se indica en la revisión final del código a continuación.
A medida que el usuario escribe en el cuadro de búsqueda, un enlace de evento de entrada llama al
el método search()
del componente con el nuevo valor del cuadro de búsqueda.
{@a asyncpipe}
AsyncPipe
El *ngFor
repite los objetos hero. Note que el *ngFor
itera sobre una lista llamada heroes$
, no sobre heroes
. El $
es una convención que indica que heroes$
es un Observable
, no un arreglo.
Como *ngFor
no puede hacer nada con un Observable
, use el
carácter de filtración (|
) seguido de async
. Esto identifica el "AsyncPipe" de Angular y se suscribe automáticamente a un "Observable" para que no tenga que
hacerlo en la clase de componente.
Editar la clase HeroSearchComponent
Reemplace la clase generada HeroSearchComponent
y los metadatos de la siguiente manera.
Observe la declaración de heroes$
como un Observable
:
Lo configurará en ngOnInit()
.
Antes de hacerlo, concéntrese en la definición de searchTerms
.
El subject RxJS searchTerms
La propiedad searchTerms
es un Subject
de RxJS.
Un Subject
es tanto una fuente de valores observables como un Observable
en sí mismo.
Puede suscribirse a un Subject
como lo haría con cualquier Observable
.
También puede insertar valores en ese Observable
llamando a su método next(value)
como lo hace el método search()
.
El evento vinculado al evento input
del cuadro de texto llama al método search()
.
Every time the user types in the textbox, the binding calls search()
with the textbox value, a "search term".
The searchTerms
becomes an Observable
emitting a steady stream of search terms.
{@a search-pipe}
Encadenamiento de operadores RxJS
Pasar un nuevo término de búsqueda directamente a searchHeroes()
después de cada pulsación de tecla del usuario crearía una cantidad excesiva de solicitudes HTTP,
gravando los recursos del servidor y quemando a través de planes de datos.
En cambio, el método ngOnInit()
filtra los searchTerms
observables a través de una secuencia de operadores RxJS que reducen el número de llamadas searchHeroes()
,
en última instancia, devuelve un observable de resultados de búsqueda de héroes oportunos (cada uno un Héroe[]
).
Aquí hay un vistazo más de cerca al código.
Cada operador funciona de la siguiente manera:
-
debounceTime(300)
espera hasta que el flujo de nuevos eventos de cadena se detenga durante 300 milisegundos antes de pasar por la última cuerda. Nunca hará solicitudes con más frecuencia que 300 ms. -
distinctUntilChanged()
asegura que una solicitud se envíe solo si el texto del filtro cambió. -
switchMap()
llama al servicio de búsqueda para cada término de búsqueda que pasa pordebounce()
ydistinctUntilChanged()
. Cancela y descarta los observables de búsqueda anteriores, devolviendo solo el último servicio de búsqueda observable.
Con el operador de switchMap,
cada evento clave que califique puede activar una llamada al método HttpClient.get()
.
Incluso con una pausa de 300 ms entre solicitudes, podría tener varias solicitudes HTTP en vuelo
y no pueden regresar en el orden enviado.
switchMap()
conserva el orden de solicitud original mientras devuelve solo lo observable de la llamada al método HTTP más reciente.
Los resultados de llamadas anteriores se cancelan y descartan.
Tenga en cuenta que cancelar un searchHeroes()
anterior observable
en realidad no aborta una solicitud HTTP pendiente.
Los resultados no deseados simplemente se descartan antes de que lleguen al código de su aplicación.
Recuerde que el componente class no se suscribe a los heroes$
observable.
Ese es el trabajo de Filtro asíncrono (asynpipe)
en la plantilla.
Intentalo
Ejecute la aplicación nuevamente. En el Tablero, ingrese texto en el cuadro de búsqueda. Si ingresas personajes que coinciden con cualquier nombre de héroe existente, verás algo como esto.
Revisión final del código
Aquí están los archivos de código discutidos en esta página (todos en la carpeta src/app/
).
{@a heroservice} {@a inmemorydataservice} {@a appmodule}
HeroService
, InMemoryDataService
, AppModule
{@a heroescomponent}
Componente de heroes
{@a herodetailcomponent}
Componete de detalles de el heroe
{@a dashboardcomponent}
Componente de panel(dashboard)
{@a herosearchcomponent}
Componente de búsqueda de héroe
Resumen
Este es el final de su viaje y ha logrado mucho.
- Agrego las dependencias necesarias para usar HTTP en la aplicación.
- Refactorizó
HeroService
para cargar héroes desde una API web. - Extendió
HeroService
para admitir los métodospost()
,put()
ydelete()
. - Actualizo los componentes para permitir agregar, editar y eliminar héroes.
- Configuro una API web en memoria.
- Aprendio a usar observables.
Esto concluye el tutorial "Tour de los Heroes". Estás listo para aprender más sobre el desarrollo Angular en la sección de fundamentos, comenzando con la guía Arquitectura guide.