Giter Site home page Giter Site logo

Comments (7)

dancornilov avatar dancornilov commented on June 13, 2024 9

Because of inactivity of this package, I created my own package which is fully compatible with this one, code base was used from this package and adjusted to new coding standards, is Angular 16 compatible.

You can try:
https://www.npmjs.com/package/@angular-magic/ngx-gp-autocomplete

from ngx-google-places-autocomplete.

lana-white avatar lana-white commented on June 13, 2024 3

Please update google places to be compatible with Angular 16!

from ngx-google-places-autocomplete.

lana-white avatar lana-white commented on June 13, 2024 3

@mfsi-samu, I've just made my own. I couldn't find anything/any plugin suitable.
I last tried Angular Magic's (ngx-gp-autocomplete), but kept getting errors with autocomplete being undefined when calling reset().
So i based my code off of this, but changed it drastically. This utilises the @types/google.maps library.

This is the Directive:

/// <reference types="@types/google.maps" />
import { Directive, ElementRef, OnInit, Output, EventEmitter, NgZone, Input, Injectable } from '@angular/core';

@Injectable({providedIn: 'root'})
@Directive({
    selector: '[google-places]',
    exportAs: 'ngx-google-places'
})
export class NgxGooglePlacesDirective implements OnInit {
    public element: HTMLInputElement;
    autocomplete?: google.maps.places.Autocomplete;
    eventListener?: google.maps.MapsEventListener;
    ngZone:NgZone;
    place?: google.maps.places.PlaceResult;
    @Output() onAddressChange: EventEmitter<any> = new EventEmitter();
    @Input() options?: google.maps.places.AutocompleteOptions;
    
    constructor(elRef: ElementRef, ngZone: NgZone) {
        //elRef will get a reference to the element where
        //the directive is placed
        this.element = elRef.nativeElement;
        this.ngZone = ngZone;
    }
  
    ngOnInit() {
        this.autocomplete = new google.maps.places.Autocomplete(this.element);
        //Event listener to monitor place changes in the input
        if (this.autocomplete.addListener !== null)
            this.eventListener = google.maps.event.addListener(this.autocomplete, 'place_changed', () => {
                this.handleChangeEvent();
        });
    }

    reset() {
        this.autocomplete?.setComponentRestrictions(this.options?.componentRestrictions || null);
        this.autocomplete?.setTypes(this.options?.types || []);
    }

    handleChangeEvent() {
        this.ngZone.run(() => {
            this.place = this.autocomplete?.getPlace();
            if (this.place) {
                this.onAddressChange.emit(this.place);
            }
        });
    }
}

This is my component (which handles the formatting etc):

import { AfterViewInit, Component, ElementRef, forwardRef, Input, isDevMode, OnInit, ViewChild } from '@angular/core';
import { AbstractControl, ControlContainer, ControlValueAccessor, FormControl, FormControlDirective, FormGroup, FormGroupDirective, NG_VALUE_ACCESSOR } from '@angular/forms';
import { map } from 'rxjs';
import { IAddressFields } from '../../models/data/form/address-fields.model';
import { IFormField } from '../../models/data/form/form-field.model';
import { filterByFieldId, getAddressFieldsByStreetId, getCountryFieldIdByStreet } from '../../utils/form-utils';
import { htmlDecode } from '../../utils/utils';

import { NgxGooglePlacesDirective } from '../../directives/ngx-google-places.directive';

@Component({
    selector: 'nova-form-field-address-block',
    template: `    
        <div class="form-floating">
              <input 
                google-places
                #placesRef="ngx-google-places"
                [options]="options"
                (change)="callbackFunction ? callbackFunction!($event) : null"
                (onAddressChange)="handleAddressChange($event)"
                type="text" 
                [id]="field.id" 
                [name]="field.id" 
                [placeholder]="lookupPlaceholderText" 
                [formControlName]="controlName" 
                class="form-control has-subtext" 
                [attr.aria-describedby]="field.id + 'Feedback'"/>                        
            <label [for]="field.id" [innerHtml]="((field.id == -20 ? 'Billing ' : field.id === -27 ? 'Shipping ' : '') + 'Address' + (field.isRequired ? ' *' : '')  | safeHtml)"></label>
             <div class="subtext">
                <p class='p-small'>Can&apos;t find your address&quest; 
                    <a (click)="toggleAddrFields($event)">Enter in manually<i [ngClass]="{'fa-chevron-down': !addressFieldsVisible, 'fa-chevron-up': addressFieldsVisible}" class="fa-solid fw-bold ms-1 text-decoration-none text-link fa-2x "></i>
                    </a>
            </p>
             </div>
            <nova-form-validation [cName]="controlName" [fGroup]="formGroup" [field]="field"></nova-form-validation> 
        </div>
    `,
    viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]
})


export class FormFieldAddressBlockComponent implements OnInit, AfterViewInit {
    @Input() field!: IFormField;
    @Input() formGroup!: FormGroup;
    @Input() controlName!: string;
    @Input() data?: any;
    @Input() callbackFunction?: (args: any) => void;
    @Input() callbackFunctionFocus?: (args: any) => void;

    addressObj: IAddressFields = {} as IAddressFields;
    lookupControl!: FormControl;
    street1Control!: FormControl;
    street2Control?: FormControl;
    suburbControl!: FormControl;
    stateControl!: FormControl;
    postcodeControl!: FormControl;
    countryControl!: FormControl;

    lookupField!: IFormField;
    street1Field!: IFormField;
    street2Field?: IFormField;
    suburbField!: IFormField;
    stateField!: IFormField;
    postcodeField!: IFormField;
    countryField!: IFormField;

    backupPlaceholderText: string = 'Start typing your address...';
    lookupPlaceholderText: string = 'Start typing your address...';
    addressFieldsVisible:boolean = false;

    //Set to false if not wanting to have addr subfields update lookup field on blur
    updateLookupField: boolean = true;

    @ViewChild("placesRef") placesRef?: NgxGooglePlacesDirective;
    options!: google.maps.places.AutocompleteOptions;
    constructor() {         
    }

    ngOnInit(): void {
       
        this.options = {
            componentRestrictions: {
                country: this.formGroup.get(getCountryFieldIdByStreet(this.field.id).toString())!.value,
            },
            
            types: [],
            //Restrict to basic data fields            
            fields: ['address_component', 'adr_address', 'business_status', 'formatted_address', 'geometry', 'icon', 'icon_mask_base_uri', 'icon_background_color', 'name', 'photo', 'place_id', 'plus_code', 'type', 'url', 'utc_offset_minutes', 'vicinity', 'wheelchair_accessible_entrance']
        };
        
    }

    ngAfterViewInit(): void {
        // this.ngxGpAutocompleteService.setOptions(this.options);
        this.addressObj = getAddressFieldsByStreetId(this.field.id);

        if (this.addressObj){
            if (this.addressObj.address) {
                this.lookupControl = this.formGroup.get(this.addressObj.address.toString()) as FormControl;
                this.lookupField = filterByFieldId(this.data.form, this.addressObj.address, false) as IFormField;
            }
            if (this.addressObj.street1) {                
                this.street1Control = this.formGroup.get(this.addressObj.street1.toString()) as FormControl;
                this.street1Field = filterByFieldId(this.data.form, this.addressObj.street1, false) as IFormField;              
            }
            if (this.addressObj.street2) {
                this.street2Control = this.formGroup.get(this.addressObj.street2.toString()) as FormControl;
                this.street2Field = filterByFieldId(this.data.form, this.addressObj.street2, false) as IFormField;
            }
            if (this.addressObj.suburb) {
                this.suburbControl = this.formGroup.get(this.addressObj.suburb.toString()) as FormControl;
                this.suburbField = filterByFieldId(this.data.form, this.addressObj.suburb, false) as IFormField;
            }
            if (this.addressObj.state) {
                this.stateControl = this.formGroup.get(this.addressObj.state.toString()) as FormControl;
                this.stateField = filterByFieldId(this.data.form, this.addressObj.state, false) as IFormField;
            }
            if (this.addressObj.postcode) {
                this.postcodeControl = this.formGroup.get(this.addressObj.postcode.toString()) as FormControl;
                this.postcodeField = filterByFieldId(this.data.form, this.addressObj.postcode, false) as IFormField;
            }          
            if (this.addressObj.country) {
                this.countryControl = this.formGroup.get(this.addressObj.country.toString()) as FormControl;
                this.countryField = filterByFieldId(this.data.form, this.addressObj.country, false) as IFormField;
            }          
        }                
              
        if (this.placesRef) {
            this.placesRef.options = this.options;
        }               
        
        this.formGroup.get(getCountryFieldIdByStreet(this.field.id).toString())?.valueChanges
            .pipe(
                map(c => c)
            )
            .subscribe({
                next: (value) => {
                    if (this.placesRef && this.placesRef.options) {
                        if (isDevMode()) {
                            console.log('new country!', value, 'current country:', this.placesRef?.options.componentRestrictions?.country);
                        }
                        if (this.options?.componentRestrictions && value && this.placesRef) {

                            if (this.placesRef.options.componentRestrictions?.country !== value) {
                                this.options.componentRestrictions.country = value;
                                this.placesRef.options.componentRestrictions!.country = value;

                                this.placesRef.reset();
                                this.resetAddrFields();

                            }
                        }
                    }
                    
                }
            })

    }
        

    handleAddressChange(address: google.maps.places.PlaceResult | any) {
        if(isDevMode()) console.log('handle addr change', address);
        let addr = address.adr_address;
        if (address.adr_address && addr && address) {
            //Get Subpremise, street_number and route
            let street1Formatted = address.adr_address?.split('locality')[0];
            //Replace span tags from address
            street1Formatted = htmlDecode(street1Formatted);
            // street1Formatted = street1Formatted.replace(/<[^>]*>?/gm, '');
            //Remove last comma in street1 address
            street1Formatted = street1Formatted.replace(/(^\s*,)|(,\s*$)/g, '');
            //Remove all HTML tags
            let result = htmlDecode(addr);
            // let result = addr.replace(/<[^>]*>?/gm, '');
            this.formGroup.get(this.controlName)?.setValue(result, { emitEvent: false });
            this.fillInAddress(address, street1Formatted);
        }
          
    }
    addrFieldsValid() {
        return this.street1Control.valid && this.street2Control?.valid && this.suburbControl.valid && this.stateControl.valid && this.postcodeControl.valid;
    }

    public toggleAddrFields(e?: any) {
        if (this.street1Field.hide) {
            if (this.updateLookupField) {
                this.detectManualAddressChange();
            }
            else {
                this.lookupControl.setValue('', { emitEvent: false });
                this.lookupPlaceholderText = this.backupPlaceholderText;
                this.lookupControl.disable({ emitEvent: false });
            }
           
            this.addressFieldsVisible = true;
            this.street1Field.hide = false;
            if (this.street2Field) this.street2Field.hide = false;
            this.suburbField.hide = false;
            this.postcodeField.hide = false;
            this.stateField.hide = false;
            this.countryField.hide = false;
            
        }
        else {
            if (this.updateLookupField) {

            }
            else {
                this.lookupPlaceholderText = '';
                this.resetAddrFields();
                this.lookupControl.enable({ emitEvent: false });
                this.lookupControl.updateValueAndValidity();
            }

            if ((this.addrFieldsValid() && this.updateLookupField) || !this.updateLookupField) {
                this.addressFieldsVisible = false;
                this.street1Field.hide = true;
                if (this.street2Field) this.street2Field.hide = true;
                this.suburbField.hide = true;
                this.postcodeField.hide = true;
                this.stateField.hide = true;
                this.countryField.hide = true;
            }
        }       
    }
   
    resetAddrFields() {               
        Object.entries(this.addressObj).forEach(([key, value]) => {
            let fc;
            fc = this.formGroup.get(value.toString());
            if (value !== this.addressObj.country) {
                fc?.setValue('', { emitEvent: false });

                fc?.updateValueAndValidity();      
            }                  
        });        
    }

    fillInAddress(address: google.maps.places.PlaceResult, street1Formatted: any) {
        // Get the place details from the autocomplete object.
        if (address && address.address_components) {
            const place = address;

            let street1 = "";
            let street2 = "";
            let suburb = "";
            let state = "";
            let postcode = "";
            //Not requiring Long/Lat at this stage. add to AddrObj etc if needed
            let locationLong = "";
            let locationLat = "";

            // Get each component of the address from the place details,
            // and then fill-in the corresponding field on the form.
            // place.address_components are google.maps.GeocoderAddressComponent objects
            // which are documented at http://goo.gle/3l5i5Mr
            for (const component of place.address_components!) {
                const componentType = component.types[0];

                switch (componentType) {
                    //Using formatted subpremise/street1address from result.addr_address for street1 field instead
                    // case "subpremise":
                    //     {
                    //         street1 = `${component.long_name}/`;
                    //         break;
                    //     }
                    // case "street_number":
                    // case "route": {
                    //     console.log('street_number:', component.long_name);
                    //     console.log('-----');
                    //     if (street1.length > 0) { street1 += " "; }
                    //     street1 += `${component.long_name}`;                    
                    //     break;
                    // }
                    case "route": {
                        street1 += `${(street1 ? ' ' : '')}${component.short_name}`;
                        break;
                    }
                    case "administrative_area_level_1":
                        state += component.short_name;
                        break;
                    case "postal_code": {
                        postcode = `${component.long_name}${postcode}`;
                        break;
                    }

                    case "postal_code_suffix": {
                        //Only really applies to US postcodes
                        postcode = `${postcode}-${component.long_name}`;
                        break;
                    }
                    case "sublocality_level_1":
                    case "locality":
                        //Allow for some suburbs not listed as locality (e.g. 876 McDonald Ave, Brooklyn in USA)
                        suburb = component.long_name;
                        break;
                }
            }

            this.street1Control.setValue(street1Formatted, { emitEvent: false });
            this.suburbControl.setValue(suburb, { emitEvent: false });
            this.stateControl.setValue(state, { emitEvent: false });
            this.postcodeControl.setValue(postcode, { emitEvent: false });
            //If required fields aren't filled in from lookup, then show addr fields and don't alow fields to hide.
            if (!this.addrFieldsValid() && this.updateLookupField) {
                this.toggleAddrFields();
            }     
        }
          
    }  
    concatAddr(st1:any, st2:any, suburb:any, state:any, postcode:any) {
        var fullAddr = '';
        fullAddr = st1 ? st1 + ', ' : '';
        fullAddr += st2 ? st2 + ', ' : '';
        fullAddr += suburb ? suburb + ', ' : '';
        fullAddr += state ? state + ' ' : '';
        fullAddr += postcode ? postcode + ', ' : '';
        fullAddr += this.countryControl.value;
        return fullAddr;
    }
    
    detectManualAddressChange() {
        let fullAddr = '';     
        var fieldElems:HTMLInputElement[] = [];
        Object.entries(this.addressObj).forEach(([key, value]) => {
            if (value !== this.addressObj.country) {
                let field: HTMLInputElement = document.getElementById(value.toString()) as HTMLInputElement;
                fieldElems.push(field);               
            }
           
        }); 
        fieldElems.forEach(field => {            
            field.addEventListener('blur', (e: any) => {               
                switch (Number(field.id)) {
                    case this.addressObj.street1: {
                        fullAddr = this.concatAddr(e?.target?.value, this.street2Control?.value, this.suburbControl.value, this.stateControl.value, this.postcodeControl.value);
                        break;
                    }
                    case this.addressObj.street2: {
                        fullAddr = this.concatAddr(this.street1Control?.value, e?.target?.value, this.suburbControl.value, this.stateControl.value, this.postcodeControl.value);
                        break;
                    }
                    case this.addressObj.suburb: {
                        fullAddr = this.concatAddr(this.street1Control?.value, this.street2Control?.value, e?.target?.value, this.stateControl.value, this.postcodeControl.value);
                        break;
                    }
                    case this.addressObj.state: {
                        fullAddr = this.concatAddr(this.street1Control?.value, this.street2Control?.value, this.suburbControl.value, e?.target?.value, this.postcodeControl.value);
                        break;
                    }
                    case this.addressObj.postcode: {
                        fullAddr = this.concatAddr(this.street1Control?.value, this.street2Control?.value, this.suburbControl.value, this.stateControl.value, e?.target?.value);
                        break;
                    }
                }
                this.lookupControl.setValue(fullAddr, {emitEvent: false})
            });
        });        
    }
}

from ngx-google-places-autocomplete.

lana-white avatar lana-white commented on June 13, 2024

I have literally written my own directive for this in about 30 mins and it works fine! Couldn't find a suitable solution as don't use Angular Material for anything.

from ngx-google-places-autocomplete.

mfsi-samu avatar mfsi-samu commented on June 13, 2024

@lana-white can you share your solution of ngx-google-places-autocomplete for angular16.

from ngx-google-places-autocomplete.

psiegers avatar psiegers commented on June 13, 2024

I was on holiday, but anyways, thanks for the posts!
I didn't actually have the time (then) to wait for any useful answer so we decided to implement an autosuggest input control based on some Material components, and the use of the Bing Maps API (specifically the Locations API at https://learn.microsoft.com/en-us/bingmaps/rest-services/locations/).
With this approach I was able to create a similar UX with a back-end API call that did the same and did not depend on any other package. It only does require you to register to get a key.
I'm unable to share the code as I rolled off the project before leaving - sorry for that - but it can be done without too many effort.

from ngx-google-places-autocomplete.

tithidutta avatar tithidutta commented on June 13, 2024

Because of inactivity of this package, I created my own package which is fully compatible with this one, code base was used from this package and adjusted to new coding standards, is Angular 16 compatible.

You can try: https://www.npmjs.com/package/@angular-magic/ngx-gp-autocomplete

No directive found with exportAs 'ngx-places'.I got this error please help. One more thing do u need ngx-google-places-autocomplete package too??

In HTML: <input
nz-input
placeholder="Search Location "
type="text"
formControlName="line2"
#placesRef="ngx-places"
ngx-gp-autocomplete
(onAddressChange)="handleAddressChange($event,i)"
/>
In ts:
@ViewChild('ngxPlaces') placesRef!: NgxGpAutocompleteDirective;

from ngx-google-places-autocomplete.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.