Elysia D.
Elysia D.

Reputation: 105

Bootstrap typeahead doesn't pass 'term' value and get "Cannot find a differ supporting object '[object Object]' of type 'object'

I have an Angular 7.2.4 application with Bootstrap 4.2.1. and I would like to call a backend service to populate an autocomplete input box but I'm having troubles even though I have already a simple bootstrap typeahead.

The html page:

<div class="form-group" [class.has-danger]="searchFailed">
    <label class="form-control-label" jhiTranslate="gatewayApp.legalAddress.fullAddress" for="field_fullAddress">Full Address</label>

    <ng-template #rt let-r="result" let-t="term">
        <ngb-highlight [result]="r.fullAddress" [term]="t"></ngb-highlight>
    </ng-template>

    <input type="text" class="form-control" name="fullAddress" id="field_fullAddress"
        [(ngModel)]="legalAddress.fullAddress" [ngbTypeahead]="searchAddress" placeholder="n°, nome via, città"
           [resultTemplate]="rt"
           [resultFormatter]="formatter"
           [inputFormatter]="formatter" />
    <span *ngIf="searching">searching...</span>
    <div class="form-control-feedback" *ngIf="searchFailed">Sorry, suggestions could not be loaded.</div>
</div>

The service:

type EntityArrayResponseType = HttpResponse<IAddress[]>;
 query(req?: any): Observable<EntityArrayResponseType> {
        console.log('[address.service.ts] query');
        const param = new HttpParams().set('searchCriteria', ADDRESS_OTHER_TYPE_CODE);
        return this.http.get<IAddress[]>(`${this.resourceUrl}/findbycriteria`, { params: param, observe: 'response' });
    }

The model:

export interface IAddress {
    id?: number;
    addressType?: string;
    ...
    fullAddress?: string;
}

export class Address implements IAddress {
    constructor(
        public id?: number,
        public addressType?: string,
        ...
        public fullAddress?: string
    ) {}
}

The controller:

 searchAddress = (text$: Observable < string >) =>
        text$.pipe(
            debounceTime(300),
            distinctUntilChanged(),
            tap(() => (this.searching = true)),
            switchMap(term =>
                this.geoLocalizationService.query(term).pipe(
                    tap(() => (this.searchFailed = false)),
                    catchError(() => {
                        this.searchFailed = true;
                        return of([]);
                    })
                )
            ),
            tap(() => (this.searching = false))
        )

    formatter = (x: {fullAddress: string}) => x.fullAddress;

Two problems:

1) Filling the fullAddress input box does trigger the searchAddress but geoLocalizationService.query always receive an empty term and send null to the underlying backend service

2) Forcing the backend service to give back however a result as a test, does make no data displayed into the input box. Getting this:

[geo-localization.service.ts] query geo-localization.service.ts:18:8
ERROR Error: "Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays."
    Angular 6
    View_NgbTypeaheadWindow_0 NgbTypeaheadWindow.html:5
    Angular 8
    node_modules ng-bootstrap.js:11024
    RxJS 41
    Angular 8
NgbTypeaheadWindow.html:5:4
    View_NgbTypeaheadWindow_0 NgbTypeaheadWindow.html:5
    Angular 5
    RxJS 5
    Angular 11
ERROR CONTEXT 
Object { view: {…}, nodeIndex: 4, nodeDef: {…}, elDef: {…}, elView: {…} }
NgbTypeaheadWindow.html:5:4
    View_NgbTypeaheadWindow_0 NgbTypeaheadWindow.html:5
    Angular 5
    RxJS 5
    Angular 11

Thanks in advance!

==========

Solved point 1 due to a silly parameter. Now I'm able to hit properly the backend service as I receive:

 [[{"place_id":48841472,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":3749006665,"boundingbox":["46...","46...","13...","13...."],"lat":"46....","lon":"13....","display_name":"---","class":"place","type":"house","importance":0.411,"address":{"house_number":"5","road":"---a","neighbourhood":"---","suburb":"---","city":"---","county":"---","state":"---","postcode":"---","country":"---","country_code":"---"}}]]

Now still having point 2 error, I think because there is something wrong with this service:

    @Injectable({ providedIn: 'root' })
export class GeoLocalizationService {
    public resourceUrl = SERVER_API_URL + 'api/geolocalization/addresses';
    constructor(protected http: HttpClient) {}

    query(req?: any): Observable<EntityArrayResponseType> {
        console.log('[geo-localization.service.ts] query' + req);
        const param = new HttpParams().set('searchString', req);
        return this.http.get<IAddress[]>(this.resourceUrl, { 
        params: param, observe: 'response' });
    }
}

May be that this is not the right way to return an Observable. Should I return it using a subscribe instead? In an other class this is working:

provinces: IDomainBean [];

ngOnInit() {
    this.legalPersonService.findAllProvinces().subscribe(
        (res: HttpResponse<IDomainBean[]>) => {
            this.provinces = res.body;
        },
        (res: HttpErrorResponse) => this.onLegalFormTypeError(res.message)
    );
}

searchProvince = (text$: Observable< string >) =>
    text$.pipe(
        debounceTime(200),
        distinctUntilChanged(),
        map(term => term === '' ? []
            : this.provinces.filter(v => v.description.toLowerCase().indexOf(term.toLowerCase()) > -1).slice(0, 10))
    )

formatter = (x: {description: string}) => x.description;

Upvotes: 2

Views: 578

Answers (1)

Elysia D.
Elysia D.

Reputation: 105

Almost resolved...now I'm able to query the service with these changes:

1) "address" instead of "address.fullAddress" in ngModel:

<input type="text" class="form-control" name="fullAddress" id="field_fullAddress"
                       [(ngModel)]="address" [ngbTypeahead]="searchAddress" placeholder="n°, nome via, città"
                       [resultTemplate]="rt"
                       [resultFormatter]="formatter"
                       [inputFormatter]="formatter" />

2) Changed the service:

    searchAddress = (text$: Observable <string>) =>
    text$.pipe(
        debounceTime(300),
        distinctUntilChanged(),
        tap(() => (this.searching = true)),
        switchMap(term =>
            this.geoService(term).pipe(
                tap(() => (this.searchFailed = false)),
                catchError(() => {
                    this.searchFailed = true;
                    return of([]);
                })
            )
        ),
        tap(() => (this.searching = false))
    )

formatter = (x: {fullAddress: string}) => x.fullAddress;

geoService(term: String): Observable <IGeoAddress []> {
    if (term === '') {
        return of([]);
    }
    return this.geoLocalizationService.queryGeoAddress(term).pipe(
        map(res => {
            return res.body.map(add => {
                const addnew = new Address(
                    //  setting initial address values  
                    this.address.idSubject,
                    this.address.idContact,
                    add.latitude,
                    add.longitude,
                    ...

                );
                return addnew;
            });
        })
    );
}

If it could help someone else or it could be improved.

Upvotes: 2

Related Questions