Implementar un type ahead con Observables en Angular y RxJS permite sugerir direcciones en tiempo real consultando la API de Google Maps. Aquí se explica, paso a paso, cómo estructurar el stream, aplicar operadores como switchMap y map, y vincular el campo de entrada con un FormControl, siguiendo buenas prácticas y sin código global.
¿Cómo funciona el type ahead con observables en Angular?
El flujo parte de un input controlado por FormControl. Cada tecla del usuario emite un valor en el stream de valueChanges, que tratamos con operadores de RxJS. Con switchMap hacemos una petición HTTP por cada cambio y desechamos anteriores; con map transformamos la respuesta a JSON y extraemos la propiedad results que devuelve Google.
Observables y RxJS: modelan cambios del input como un stream continuo.
Operador switchMap: intercambia la suscripción activa por la última consulta.
Operador map encadenado: primero convierte a response.json(), luego extrae response.results.
Convención $: nombrar el stream como results$ para indicar que es observable.
Tipos flexibles: usar any cuando el contrato de la API puede cambiar.
Buenas prácticas: mover importaciones al componente y evitar código global.
Sin jQuery: referenciar el input con FormControl, no con jQuery o JavaScript puro.
Template strings: armar la URL con backticks y ${variables} para parámetros de consulta.
¿Cómo configurar FormControl y el stream de resultados?
Primero se declara un searchField como FormControl y un results$ como Observable. En el constructor, se inicializa searchField y se construye el pipeline que escucha valueChanges, llama a http.get con el query y aplica los maps. También se define una constante URL para la ruta del servicio de Google.
import{FormControl}from'@angular/forms';import{Observable}from'rxjs/Observable';// Operadores RxJS agregados a Observable (switchMap, map), según configuración del proyectoimport{Http}from'@angular/http';exportclassCrearComponent{private searchField:FormControl; results$:Observable<any>;privateURL='...';// URL base del servicio de Google.constructor(private http:Http){this.searchField=newFormControl();this.results$=this.searchField.valueChanges.switchMap(query =>this.http.get(`${this.URL}?address=${query}`)).map(response => response.json()).map(response => response.results);}}
Claves para dominar este paso:
searchField como FormControl: habilita escuchar cambios con valueChanges.
results$ como stream: retorna sugerencias conforme se escribe.
http.get con query: address=query es el parámetro que consume Google.
Encadenamiento de operadores: se pueden usar múltiples map para formatear datos.
¿Por qué usar switchMap en lugar de otras estrategias?
Porque cancela peticiones previas cuando llega una nueva entrada del usuario. Así evitas respuestas obsoletas y mantienes el stream sincronizado con lo último tecleado.
¿Qué conviene tipar y qué dejar como any?
Cuando la estructura exacta de la respuesta de la API puede variar, any reduce fricción. Se mapea lo necesario: response.json() y response.results. Esto simplifica y protege ante cambios del proveedor.
¿Cómo vincular el input con formControl en HTML?
Se crea un contenedor visual y un formulario simple. El input se enlaza con [formControl] para que Angular emita cambios al stream. La interfaz todavía no lista las sugerencias; esa capa visual se añade después, pero el type ahead ya queda operativo a nivel de datos.
<divclass="well"><form><h3>Buscar dirección en Google Maps</h3><inputtype="text"[formControl]="searchField"class="form-control"></form></div>
Puntos importantes del marcado:
[formControl]="searchField": conecta el input con el FormControl de TypeScript.
Estructura mínima: contenedor well, form, encabezado y campo de texto.
Estilos: class="form-control" para mejor presentación.
¿Qué prácticas mejoran la mantenibilidad?
Evitar código global: mueve importaciones y lógica al componente donde se usa.
Inyección de dependencias: private http: Http dentro del constructor.
Separación de responsabilidades: lógica del stream en TypeScript, vista en HTML.
¿Has probado otras combinaciones de operadores para tu type ahead con RxJS? Comparte tu experiencia y dudas en los comentarios.
Cómo implementar type ahead con Observables y RxJS
A mi me salio un error con FormControl, lo solucione ingresando a APP.MODULE.TS en import ReactiveFormsModule dentro de @angular/forms y de la misma forma colocar ReactiveFormsModule en los imports de NgModule. Espero que les sirva.
hola buenas tardes,
si estas intentando instalar el google map en angular 6 no sirve, es solo para angular 4, recuerda tambien que todos los componentes que creemos deben estar registrados en el module.ts ,revisalo y me cuentas.
Ahora tienes que usar HttpClient, para usarlo debes incluir dentro de tu app.module.ts el modulo HttpClientModule y luego en el construcción de tu servicio debe incluir httpClient que es importando desde '@angular/common/http'.
Hola, es buena practica si toda la logica es necesaria para la inicializacion del objeto, si son cosas que no necesariamente deben ejecutarse, ya comienza a generar mucha carga y no es buena idea.
¿Algún consejo para implementar el directions API?
Angular 6 – 24/11/2018 , para que el input (componente reactivo) funcione tienen que irse a app.module.ts e importar :
ReactiveFormsModule
dejo el codigo:
import{BrowserModule}from'@angular/platform-browser';import{NgModule}from'@angular/core';import{AppComponent}from'./app.component';import{FormsModule,ReactiveFormsModule}from'@angular/forms';import{AgmCoreModule}from'@agm/core';//mapsimport{ResaltarDirective}from'./directives/resaltar.directive';import{ContarClicksDirective}from'./directives/contar-clicks.directive';import{BrowserAnimationsModule}from"@angular/platform-browser/animations";import{Routes,RouterModule}from'@angular/router';import{DetalleComponent}from'./detalle/detalle.component';import{LugaresComponent}from'./lugares/lugares.component';import{ContactoComponent}from'./contacto/contacto.component';import{LugaresService}from'./services/lugares.service';import{CrearComponent}from'./crear/crear.component';import{AngularFireModule}from'angularfire2';import{ environment }from'../environments/environment';import{AngularFirestoreModule}from'@angular/fire/firestore';import{AngularFireStorageModule}from'@angular/fire/storage';import{AngularFireAuthModule}from"angularfire2/auth";import{HttpClientModule}from'@angular/common/http';import{LinkifystrPipe}from'./pipe/linkifystr.pipe';import{LoginComponent}from'./login/login.component';import{RegistroComponent}from'./registro/registro.component';import{AutorizacionService}from"./services/autorizacion.service";import{MyGuard}from'./services/my-guard.service';constappRoutes:Routes=[{path:"",component:LugaresComponent},{path:"lugares",component:LugaresComponent},{path:"detalle/:id",component:DetalleComponent},{path:"contacto",component:ContactoComponent},{path:"crear/:id",component:CrearComponent,canActivate:[MyGuard]},{path:"login",component:LoginComponent},{path:"registro",component:RegistroComponent},];@NgModule({declarations:[AppComponent,ResaltarDirective,ContarClicksDirective,DetalleComponent,LugaresComponent,ContactoComponent,CrearComponent,LinkifystrPipe,LoginComponent,RegistroComponent],imports:[BrowserModule,FormsModule,AgmCoreModule.forRoot({apiKey:''}),BrowserAnimationsModule,RouterModule.forRoot(appRoutes),AngularFireModule.initializeApp(environment.firebase),AngularFirestoreModule,// imports firebase/firestore, only needed for database featuresAngularFireAuthModule,// imports firebase/auth, only needed for auth features,AngularFireStorageModule,// imports firebase/storage only needed for storage featuresHttpClientModule,BrowserAnimationsModule,ReactiveFormsModule],providers:[LugaresService,AutorizacionService,MyGuard],bootstrap:[AppComponent]})exportclassAppModule{}